Compare commits
148 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
6a7019ec1a | |
![]() |
7375986a8e | |
![]() |
251936dcdc | |
![]() |
921c38cea2 | |
![]() |
718f255817 | |
![]() |
433b60e720 | |
![]() |
b9a4402431 | |
![]() |
daaf393872 | |
![]() |
c8cae1b200 | |
![]() |
f70c93715b | |
![]() |
60a293514d | |
![]() |
8312ba1d6c | |
![]() |
eea5e368a9 | |
![]() |
759beaf940 | |
![]() |
9237de9b89 | |
![]() |
995ab69b1e | |
![]() |
a072752609 | |
![]() |
d28f504a80 | |
![]() |
e82a457a2c | |
![]() |
256b3354ca | |
![]() |
be39b534c1 | |
![]() |
274198a7bd | |
![]() |
439d48f8a3 | |
![]() |
8aeb8b006e | |
![]() |
039c9c3da4 | |
![]() |
9bc43e2efc | |
![]() |
4dd1b4aae6 | |
![]() |
3358fd7942 | |
![]() |
221aa3a5b9 | |
![]() |
9e7ec0cea7 | |
![]() |
98261e3eba | |
![]() |
1ae7fc8613 | |
![]() |
2874f01c34 | |
![]() |
96be359eab | |
![]() |
0f641c0f08 | |
![]() |
31f40a5e15 | |
![]() |
e8d1bfcad9 | |
![]() |
6b73235ba3 | |
![]() |
ce56f86f0d | |
![]() |
03f67de289 | |
![]() |
5173cac28f | |
![]() |
c5c018ac0f | |
![]() |
3cfc7032bc | |
![]() |
838a03086e | |
![]() |
2ebf10e9ba | |
![]() |
60eeaaf3eb | |
![]() |
5b294b781b | |
![]() |
3022814f8a | |
![]() |
21efb45431 | |
![]() |
f7fb309680 | |
![]() |
d45bdbeeda | |
![]() |
ed014e9304 | |
![]() |
289f817180 | |
![]() |
a7b561baf5 | |
![]() |
49f8a69675 | |
![]() |
3c2f4e891d | |
![]() |
c442dcfeb3 | |
![]() |
2cb246ec3e | |
![]() |
3dd0b2fd8a | |
![]() |
ba74b86448 | |
![]() |
0ee9965612 | |
![]() |
285ea37245 | |
![]() |
53d067f0d1 | |
![]() |
a905850345 | |
![]() |
eb36dad968 | |
![]() |
d553a75d09 | |
![]() |
38a5bd4bd0 | |
![]() |
bee36e1260 | |
![]() |
5ac78cfd18 | |
![]() |
ce21d13774 | |
![]() |
dc3405876e | |
![]() |
8c33d1b5b3 | |
![]() |
e314ff0d3d | |
![]() |
e8bb046ae6 | |
![]() |
0e07e3f155 | |
![]() |
55c00abf40 | |
![]() |
b9a79c943c | |
![]() |
aa00e053ef | |
![]() |
95d97f1b37 | |
![]() |
e0b844f0a3 | |
![]() |
f5c8515803 | |
![]() |
56b7dd9ac7 | |
![]() |
41b0298e6a | |
![]() |
481c03937a | |
![]() |
20cee7db43 | |
![]() |
8f085b27fd | |
![]() |
8608ee5385 | |
![]() |
4ff06b7b9f | |
![]() |
05d6a7b586 | |
![]() |
52f9f419e0 | |
![]() |
718756b538 | |
![]() |
9b3052774b | |
![]() |
02c8872b67 | |
![]() |
81f571ec15 | |
![]() |
6ac729663f | |
![]() |
9d08cc0cf5 | |
![]() |
23235f33bc | |
![]() |
8b9aca0665 | |
![]() |
0e2c295789 | |
![]() |
2eb44dd44e | |
![]() |
332a7d5b25 | |
![]() |
9f0e033db2 | |
![]() |
10c33fc587 | |
![]() |
c1d7c1a1b2 | |
![]() |
bdb412b039 | |
![]() |
286a7cc717 | |
![]() |
a532876a43 | |
![]() |
03a3ae4bda | |
![]() |
aa05136933 | |
![]() |
865f7d53d0 | |
![]() |
cd96319f97 | |
![]() |
90f44405ba | |
![]() |
38a5d315aa | |
![]() |
bc4a63f72f | |
![]() |
77b67eedb9 | |
![]() |
905d001e89 | |
![]() |
d2b0a2e777 | |
![]() |
81c08aadac | |
![]() |
05c3625ed3 | |
![]() |
23a33b27d6 | |
![]() |
8b87d79f47 | |
![]() |
14e48d2c6e | |
![]() |
188e3f1c6a | |
![]() |
b4ccd9e353 | |
![]() |
c6e9ee77e7 | |
![]() |
c6a5a64419 | |
![]() |
365149beb4 | |
![]() |
367252196c | |
![]() |
8758556822 | |
![]() |
32c184ecbe | |
![]() |
debd824a69 | |
![]() |
25322e0bb0 | |
![]() |
a61779b7dc | |
![]() |
ffd10f7d29 | |
![]() |
00a7348e77 | |
![]() |
fa98640a36 | |
![]() |
7edde21472 | |
![]() |
df14656d8e | |
![]() |
9601818407 | |
![]() |
13361b55ef | |
![]() |
7880389033 | |
![]() |
6e54b705f7 | |
![]() |
2f1fe92a4f | |
![]() |
267dc854d8 | |
![]() |
6592cfde3a | |
![]() |
05c0ab1db2 | |
![]() |
5c1ee6bd0b | |
![]() |
16e7be1234 |
12
.babelrc
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false,
|
||||
"targets": {
|
||||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
||||
}
|
||||
}],
|
||||
"stage-2"
|
||||
],
|
||||
"plugins": ["transform-vue-jsx", "transform-runtime"]
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
|
@ -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
|
|
@ -1,4 +1,23 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log
|
||||
.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?
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||
|
||||
module.exports = {
|
||||
"plugins": {
|
||||
// to edit target browsers: use "browserslist" field in package.json
|
||||
"postcss-import": {},
|
||||
"autoprefixer": {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
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
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
223
README.md
|
@ -1,193 +1,80 @@
|
|||
# manage-system #
|
||||
基于Vue.js 2.x系列 + Element UI 的后台管理系统解决方案。[线上地址](http://blog.gdfengshuo.com/example/work/)
|
||||
# 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="https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
|
||||
</a>
|
||||
|
||||
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上演示](https://lin-xin.github.io/example/vue-manage-system/)
|
||||
|
||||
> 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)
|
||||
|
||||
## 捐赠
|
||||

|
||||
## 赞助商
|
||||
|
||||
## 前言 ##
|
||||
之前在公司用了Vue + Element组件库做了个后台管理系统,基本很多组件可以直接引用组件库的,但是也有一些需求无法满足。像图片裁剪上传、富文本编辑器、图表等这些在后台管理系统中很常见的功能,就需要引用其他的组件才能完成。从寻找组件,到使用组件的过程中,遇到了很多问题,也积累了宝贵的经验。所以我就把开发这个后台管理系统的经验,总结成这个后台管理系统解决方案。
|
||||
### 好问
|
||||
|
||||
该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统(Web Management System)开发。基于vue.js,使用vue-cli脚手架快速生成项目目录,引用Element UI组件库,方便开发快速简洁好看的组件。分离颜色样式,支持手动切换主题色,而且很方便使用自定义主题色。
|
||||
[<img src="https://static.bestqa.net/logo/bestqa_haowen.png" width="220" height="100">](https://www.bestqa.net/home/index.html)
|
||||
|
||||
## 功能 ##
|
||||
- [x] Element UI
|
||||
- [x] 登录/注销
|
||||
- [x] 表格
|
||||
- [x] 表单
|
||||
- [x] 图表 :bar_chart:
|
||||
- [x] 富文本编辑器
|
||||
- [x] markdown编辑器
|
||||
- [x] 图片拖拽/裁剪上传
|
||||
- [x] 支持切换主题色 :sparkles:
|
||||
- [x] 列表拖拽排序
|
||||
专业问卷服务,一对一客服,按需定制
|
||||
|
||||
## 支持作者
|
||||
|
||||
## 目录结构介绍 ##
|
||||
请作者喝杯咖啡吧!(微信号:linxin_20)
|
||||
|
||||
|-- build // webpack配置文件
|
||||
|-- config // 项目打包路径
|
||||
|-- src // 源码目录
|
||||
| |-- components // 组件
|
||||
| |-- common // 公共组件
|
||||
| |-- bus.js // Event Bus
|
||||
| |-- Header.vue // 公共头部
|
||||
| |-- Home.vue // 公共路由入口
|
||||
| |-- Sidebar.vue // 公共左边栏
|
||||
| |-- page // 主要路由页面
|
||||
| |-- BaseCharts.vue // 基础图表
|
||||
| |-- BaseForm.vue // 基础表单
|
||||
| |-- BaseTable.vue // 基础表格
|
||||
| |-- DragList.vue // 拖拽列表组件
|
||||
| |-- Login.vue // 登录
|
||||
| |-- Markdown.vue // markdown组件
|
||||
| |-- Premission.vue // 权限测试组件
|
||||
| |-- Readme.vue // 自述组件
|
||||
| |-- Upload.vue // 图片上传
|
||||
| |-- VueEditor.vue // 富文本编辑器
|
||||
| |-- VueTable.vue // datasource表格组件
|
||||
| |-- App.vue // 页面入口文件
|
||||
| |-- main.js // 程序入口文件,加载各种公共组件
|
||||
|-- .babelrc // ES6语法编译配置
|
||||
|-- .editorconfig // 代码编写规格
|
||||
|-- .gitignore // 忽略的文件
|
||||
|-- index.html // 入口html文件
|
||||
|-- package.json // 项目及工具的依赖配置文件
|
||||
|-- README.md // 说明
|
||||

|
||||
|
||||
## 前言
|
||||
|
||||
## 安装步骤 ##
|
||||
该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia + typescript,引用 Element Plus 组件库,方便开发。实现逻辑简单,适合外包项目,快速交付。
|
||||
|
||||
git clone https://github.com/lin-xin/vue-manage-system.git // 把模板下载到本地
|
||||
cd vue-manage-system // 进入模板目录
|
||||
npm install // 安装项目依赖,等待安装完成之后
|
||||
## 功能
|
||||
|
||||
## 本地开发 ##
|
||||
- [x] Element Plus
|
||||
- [x] vite 3
|
||||
- [x] pinia
|
||||
- [x] typescript
|
||||
- [x] 登录/注册
|
||||
- [x] Dashboard
|
||||
- [x] 表格/表单
|
||||
- [x] 图表 :bar_chart:
|
||||
- [x] 富文本/markdown 编辑器
|
||||
- [x] 图片拖拽/裁剪上传
|
||||
- [x] 权限管理
|
||||
- [x] 三级菜单
|
||||
- [x] 自定义图标
|
||||
- [x] 主题切换
|
||||
|
||||
// 开启服务器,浏览器访问 http://localhost:8080
|
||||
npm run dev
|
||||
## 安装步骤
|
||||
|
||||
## 构建生产 ##
|
||||
> 因为使用 vite3,node 版本需要 14.18+
|
||||
|
||||
// 执行构建命令,生成的dist文件夹放在服务器下即可访问
|
||||
npm run build
|
||||
```
|
||||
git clone https://github.com/lin-xin/vue-manage-system.git // 把模板下载到本地
|
||||
cd vue-manage-system // 进入模板目录
|
||||
npm install // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn
|
||||
|
||||
## 组件使用说明与演示 ##
|
||||
// 运行
|
||||
npm run dev
|
||||
|
||||
### 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>
|
||||
|
||||
```JavaScript
|
||||
<template>
|
||||
<div>
|
||||
<schart :canvasId="canvasId"
|
||||
:type="type"
|
||||
:width="width"
|
||||
:height="height"
|
||||
: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', // 图表类型
|
||||
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'
|
||||
}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Schart
|
||||
}
|
||||
}
|
||||
</script>
|
||||
// 执行构建命令,生成的dist文件夹放在服务器下即可访问
|
||||
npm run build
|
||||
```
|
||||
|
||||
### element-ui ###
|
||||
一套基于vue.js2.0的桌面组件库。访问地址:[element](http://element.eleme.io/#/zh-CN/component/layout)
|
||||
## 项目截图
|
||||
|
||||
### vue-datasource ###
|
||||
一个用于动态创建表格的vue.js服务端组件。访问地址:[vue-datasource](https://github.com/coderdiaz/vue-datasource)
|
||||
|
||||
### Vue-Quill-Editor ###
|
||||
基于Quill、适用于Vue2的富文本编辑器。访问地址:[vue-quill-editor](https://github.com/surmon-china/vue-quill-editor)
|
||||
|
||||
(IE10及以下不支持)
|
||||
|
||||
### mavonEditor ###
|
||||
基于Vue的markdown编辑器。访问地址:[mavonEditor](https://github.com/hinesboy/mavonEditor)
|
||||
|
||||
### vue-cropperjs ###
|
||||
一个封装了 cropperjs 的 Vue 组件,用于裁剪图片。访问地址:[vue-cropperjs](https://github.com/Agontuk/vue-cropperjs)
|
||||
|
||||
## 其他注意事项 ##
|
||||
### 一、如果我不想用到上面的某些组件呢,那我怎么在模板中删除掉不影响到其他功能呢? ###
|
||||
|
||||
举个栗子,我不想用 vue-datasource 这个组件,那我需要分四步走。
|
||||
|
||||
第一步:删除该组件的路由,在目录 src/router/index.js 中,找到引入改组件的路由,删除下面这段代码。
|
||||
|
||||
```JavaScript
|
||||
{
|
||||
path: '/vuetable',
|
||||
component: resolve => require(['../components/page/VueTable.vue'], resolve) // vue-datasource组件
|
||||
},
|
||||
```
|
||||
|
||||
第二步:删除引入该组件的文件。在目录 src/components/page/ 删除 VueTable.vue 文件。
|
||||
|
||||
第三步:删除该页面的入口。在目录 src/components/common/Sidebar.vue 中,找到该入口,删除下面这段代码。
|
||||
|
||||
```HTML
|
||||
<el-menu-item index="vuetable">Vue表格组件</el-menu-item>
|
||||
```
|
||||
|
||||
第四步:卸载该组件。执行以下命令:
|
||||
|
||||
npm un vue-datasource -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 属性去掉即可。
|
||||
|
||||
## 项目截图 ##
|
||||
### 默认皮肤 ###
|
||||
### 首页
|
||||
|
||||

|
||||
|
||||
### 浅绿色皮肤 ###
|
||||
### 登录
|
||||
|
||||

|
||||

|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)
|
||||
|
|
246
README_EN.md
|
@ -1,187 +1,119 @@
|
|||
# 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
|
||||

|
||||
|
||||
## 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.
|
||||

|
||||
|
||||
## Function ##
|
||||
- [x] Element-UI
|
||||
- [x] Login/Logout
|
||||
- [x] Table
|
||||
- [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
|
||||
## 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.
|
||||
|
||||
## Directory structure ##
|
||||
## Function
|
||||
|
||||
|-- build // Webpack configuration file
|
||||
|-- config // Project package path
|
||||
|-- src // Source directory
|
||||
| |-- components // Components
|
||||
| |-- common // Common component
|
||||
| |-- bus.js // Event Bus
|
||||
| |-- Header.vue // Header component
|
||||
| |-- Home.vue // Home component
|
||||
| |-- Sidebar.vue // Sidebar component
|
||||
| |-- page // Router page
|
||||
| |-- BaseCharts.vue // BaseCharts
|
||||
| |-- BaseForm.vue // BaseForm
|
||||
| |-- BaseTable.vue // BaseTable
|
||||
| |-- Login.vue // Login
|
||||
| |-- DragList.vue
|
||||
| |-- Markdown.vue // Markdown
|
||||
| |-- Premission.vue
|
||||
| |-- Readme.vue // Readme
|
||||
| |-- Upload.vue // Upload
|
||||
| |-- VueEditor.vue // VueEditor
|
||||
| |-- VueTable.vue // VueTable
|
||||
| |-- App.vue // Main component
|
||||
| |-- main.js // Entry file
|
||||
|-- .babelrc // ES6 syntax compiler configuration
|
||||
|-- .editorconfig // Code specification
|
||||
|-- .gitignore // Ignored file
|
||||
|-- index.html // Entry HTML file
|
||||
|-- package.json // Dependent configuration file
|
||||
|-- README.md // Readme
|
||||
- [x] Element-UI
|
||||
- [x] Login/Logout
|
||||
- [x] Dashboard
|
||||
- [x] Table
|
||||
- [x] Tabs
|
||||
- [x] From
|
||||
- [x] Chart :bar_chart:
|
||||
- [x] Editor
|
||||
- [x] Markdown
|
||||
- [x] Upload pictures by clipping or dragging
|
||||
- [x] Permission
|
||||
- [x] Three level menu
|
||||
- [x] Custom icon
|
||||
|
||||
## Installation steps
|
||||
|
||||
## Installation steps ##
|
||||
git clone https://github.com/lin-xin/vue-manage-system.git // Clone templates
|
||||
cd vue-manage-system // Enter template directory
|
||||
npm install // Installation dependency
|
||||
|
||||
git clone https://github.com/lin-xin/vue-manage-system.git // Clone templates
|
||||
cd vue-manage-system // Enter template directory
|
||||
npm install // Installation dependency
|
||||
## Local development
|
||||
|
||||
## Local development ##
|
||||
npm run dev
|
||||
|
||||
// Open server and access http://localhost:8080 in browser
|
||||
npm run dev
|
||||
## Constructing production
|
||||
|
||||
## Constructing production ##
|
||||
// Constructing project
|
||||
npm run build
|
||||
|
||||
// Constructing project
|
||||
npm run build
|
||||
## Component description and presentation
|
||||
|
||||
## Component description and presentation ##
|
||||
### vue-schart
|
||||
|
||||
### vue-schart ###
|
||||
Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/linxin/vue-schart)
|
||||
Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/vue-schart#/)
|
||||
|
||||
```JavaScript
|
||||
```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-datasource ###
|
||||
A Vue.js server side component to create dynamic tables. Github : [vue-datasource](https://github.com/coderdiaz/vue-datasource)
|
||||
|
||||
### 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-datasource 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: '/vuetable',
|
||||
component: resolve => require(['../components/page/VueTable.vue'], resolve)
|
||||
},
|
||||
```
|
||||
|
||||
Second,delete the component files. Enter 'src/components/page/' and delete 'VueTable.vue' file.
|
||||
|
||||
The third step is to delete the entry. Enter 'src/components/common/Sidebar.vue' and delete the code below.
|
||||
|
||||
```HTML
|
||||
<el-menu-item index="vuetable">Vue表格组件</el-menu-item>
|
||||
```
|
||||
|
||||
Finally, uninstall this component.
|
||||
|
||||
npm un vue-datasource -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
|
||||
|
||||

|
||||
|
||||
### Green theme ###
|
||||
### Login
|
||||
|
||||

|
||||

|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// Generated by 'unplugin-auto-import'
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
'use strict'
|
||||
require('./check-versions')()
|
||||
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
const ora = require('ora')
|
||||
const rm = require('rimraf')
|
||||
const path = require('path')
|
||||
const chalk = require('chalk')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const webpackConfig = require('./webpack.prod.conf')
|
||||
|
||||
const spinner = ora('building for production...')
|
||||
spinner.start()
|
||||
|
||||
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
|
||||
if (err) throw err
|
||||
webpack(webpackConfig, (err, stats) => {
|
||||
spinner.stop()
|
||||
if (err) throw err
|
||||
process.stdout.write(stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}) + '\n\n')
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
console.log(chalk.red(' Build failed with errors.\n'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(chalk.cyan(' Build complete.\n'))
|
||||
console.log(chalk.yellow(
|
||||
' Tip: built files are meant to be served over an HTTP server.\n' +
|
||||
' Opening index.html over file:// won\'t work.\n'
|
||||
))
|
||||
})
|
||||
})
|
|
@ -1,54 +0,0 @@
|
|||
'use strict'
|
||||
const chalk = require('chalk')
|
||||
const semver = require('semver')
|
||||
const packageConfig = require('../package.json')
|
||||
const shell = require('shelljs')
|
||||
|
||||
function exec (cmd) {
|
||||
return require('child_process').execSync(cmd).toString().trim()
|
||||
}
|
||||
|
||||
const versionRequirements = [
|
||||
{
|
||||
name: 'node',
|
||||
currentVersion: semver.clean(process.version),
|
||||
versionRequirement: packageConfig.engines.node
|
||||
}
|
||||
]
|
||||
|
||||
if (shell.which('npm')) {
|
||||
versionRequirements.push({
|
||||
name: 'npm',
|
||||
currentVersion: exec('npm --version'),
|
||||
versionRequirement: packageConfig.engines.npm
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
const warnings = []
|
||||
|
||||
for (let i = 0; i < versionRequirements.length; i++) {
|
||||
const mod = versionRequirements[i]
|
||||
|
||||
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
||||
warnings.push(mod.name + ': ' +
|
||||
chalk.red(mod.currentVersion) + ' should be ' +
|
||||
chalk.green(mod.versionRequirement)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (warnings.length) {
|
||||
console.log('')
|
||||
console.log(chalk.yellow('To use this template, you must update following to modules:'))
|
||||
console.log()
|
||||
|
||||
for (let i = 0; i < warnings.length; i++) {
|
||||
const warning = warnings[i]
|
||||
console.log(' ' + warning)
|
||||
}
|
||||
|
||||
console.log()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
BIN
build/logo.png
Before Width: | Height: | Size: 6.7 KiB |
101
build/utils.js
|
@ -1,101 +0,0 @@
|
|||
'use strict'
|
||||
const path = require('path')
|
||||
const config = require('../config')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const packageConfig = require('../package.json')
|
||||
|
||||
exports.assetsPath = function (_path) {
|
||||
const assetsSubDirectory = process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsSubDirectory
|
||||
: config.dev.assetsSubDirectory
|
||||
|
||||
return path.posix.join(assetsSubDirectory, _path)
|
||||
}
|
||||
|
||||
exports.cssLoaders = function (options) {
|
||||
options = options || {}
|
||||
|
||||
const cssLoader = {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: options.sourceMap
|
||||
}
|
||||
}
|
||||
|
||||
const postcssLoader = {
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: options.sourceMap
|
||||
}
|
||||
}
|
||||
|
||||
// generate loader string to be used with extract text plugin
|
||||
function generateLoaders (loader, loaderOptions) {
|
||||
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
|
||||
|
||||
if (loader) {
|
||||
loaders.push({
|
||||
loader: loader + '-loader',
|
||||
options: Object.assign({}, loaderOptions, {
|
||||
sourceMap: options.sourceMap
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Extract CSS when that option is specified
|
||||
// (which is the case during production build)
|
||||
if (options.extract) {
|
||||
return ExtractTextPlugin.extract({
|
||||
use: loaders,
|
||||
fallback: 'vue-style-loader'
|
||||
})
|
||||
} else {
|
||||
return ['vue-style-loader'].concat(loaders)
|
||||
}
|
||||
}
|
||||
|
||||
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
|
||||
return {
|
||||
css: generateLoaders(),
|
||||
postcss: generateLoaders(),
|
||||
less: generateLoaders('less'),
|
||||
sass: generateLoaders('sass', { indentedSyntax: true }),
|
||||
scss: generateLoaders('sass'),
|
||||
stylus: generateLoaders('stylus'),
|
||||
styl: generateLoaders('stylus')
|
||||
}
|
||||
}
|
||||
|
||||
// Generate loaders for standalone style files (outside of .vue)
|
||||
exports.styleLoaders = function (options) {
|
||||
const output = []
|
||||
const loaders = exports.cssLoaders(options)
|
||||
|
||||
for (const extension in loaders) {
|
||||
const loader = loaders[extension]
|
||||
output.push({
|
||||
test: new RegExp('\\.' + extension + '$'),
|
||||
use: loader
|
||||
})
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
exports.createNotifierCallback = () => {
|
||||
const notifier = require('node-notifier')
|
||||
|
||||
return (severity, errors) => {
|
||||
if (severity !== 'error') return
|
||||
|
||||
const error = errors[0]
|
||||
const filename = error.file && error.file.split('!').pop()
|
||||
|
||||
notifier.notify({
|
||||
title: packageConfig.name,
|
||||
message: severity + ': ' + error.name,
|
||||
subtitle: filename || '',
|
||||
icon: path.join(__dirname, 'logo.png')
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
'use strict'
|
||||
const utils = require('./utils')
|
||||
const config = require('../config')
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
const sourceMapEnabled = isProduction
|
||||
? config.build.productionSourceMap
|
||||
: config.dev.cssSourceMap
|
||||
|
||||
module.exports = {
|
||||
loaders: utils.cssLoaders({
|
||||
sourceMap: sourceMapEnabled,
|
||||
extract: isProduction
|
||||
}),
|
||||
cssSourceMap: sourceMapEnabled,
|
||||
cacheBusting: config.dev.cacheBusting,
|
||||
transformToRequire: {
|
||||
video: ['src', 'poster'],
|
||||
source: 'src',
|
||||
img: 'src',
|
||||
image: 'xlink:href'
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const config = require('../config')
|
||||
const vueLoaderConfig = require('./vue-loader.conf')
|
||||
|
||||
function resolve (dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
context: path.resolve(__dirname, '../'),
|
||||
entry: {
|
||||
app: './src/main.js'
|
||||
},
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: '[name].js',
|
||||
publicPath: process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsPublicPath
|
||||
: config.dev.assetsPublicPath
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js',
|
||||
'@': resolve('src'),
|
||||
'static': path.resolve(__dirname, '../static'),
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
include: [resolve('src'), resolve('test')]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('img/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('media/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
node: {
|
||||
// prevent webpack from injecting useless setImmediate polyfill because Vue
|
||||
// source contains it (although only uses it if it's native).
|
||||
setImmediate: false,
|
||||
// prevent webpack from injecting mocks to Node native modules
|
||||
// that does not make sense for the client
|
||||
dgram: 'empty',
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty'
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
'use strict'
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const portfinder = require('portfinder')
|
||||
|
||||
const HOST = process.env.HOST
|
||||
const PORT = process.env.PORT && Number(process.env.PORT)
|
||||
|
||||
const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
|
||||
},
|
||||
// cheap-module-eval-source-map is faster for development
|
||||
devtool: config.dev.devtool,
|
||||
|
||||
// these devServer options should be customized in /config/index.js
|
||||
devServer: {
|
||||
clientLogLevel: 'warning',
|
||||
historyApiFallback: true,
|
||||
hot: true,
|
||||
compress: true,
|
||||
host: HOST || config.dev.host,
|
||||
port: PORT || config.dev.port,
|
||||
open: config.dev.autoOpenBrowser,
|
||||
overlay: config.dev.errorOverlay
|
||||
? { warnings: false, errors: true }
|
||||
: false,
|
||||
publicPath: config.dev.assetsPublicPath,
|
||||
proxy: config.dev.proxyTable,
|
||||
quiet: true, // necessary for FriendlyErrorsPlugin
|
||||
watchOptions: {
|
||||
poll: config.dev.poll,
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': require('../config/dev.env')
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
inject: true
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
module.exports = new Promise((resolve, reject) => {
|
||||
portfinder.basePort = process.env.PORT || config.dev.port
|
||||
portfinder.getPort((err, port) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
// publish the new Port, necessary for e2e tests
|
||||
process.env.PORT = port
|
||||
// add port to devServer config
|
||||
devWebpackConfig.devServer.port = port
|
||||
|
||||
// Add FriendlyErrorsPlugin
|
||||
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
|
||||
compilationSuccessInfo: {
|
||||
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
|
||||
},
|
||||
onErrors: config.dev.notifyOnErrors
|
||||
? utils.createNotifierCallback()
|
||||
: undefined
|
||||
}))
|
||||
|
||||
resolve(devWebpackConfig)
|
||||
}
|
||||
})
|
||||
})
|
|
@ -1,29 +0,0 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
vendor: ['vue/dist/vue.common.js','vue-router', 'babel-polyfill','axios']
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, '../static/js'),
|
||||
filename: '[name].dll.js',
|
||||
library: '[name]_library'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DllPlugin({
|
||||
path: path.join(__dirname, '.', '[name]-manifest.json'),
|
||||
name: '[name]_library'
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
|
@ -1,145 +0,0 @@
|
|||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||
|
||||
const env = require('../config/prod.env')
|
||||
|
||||
const webpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
extract: true,
|
||||
usePostCSS: true
|
||||
})
|
||||
},
|
||||
devtool: config.build.productionSourceMap ? config.build.devtool : false,
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: utils.assetsPath('js/[name].[chunkhash].js'),
|
||||
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
|
||||
},
|
||||
plugins: [
|
||||
// http://vuejs.github.io/vue-loader/en/workflow/production.html
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': env
|
||||
}),
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
},
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
parallel: true
|
||||
}),
|
||||
// extract css into its own file
|
||||
new ExtractTextPlugin({
|
||||
filename: utils.assetsPath('css/[name].[contenthash].css'),
|
||||
// Setting the following option to `false` will not extract CSS from codesplit chunks.
|
||||
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
|
||||
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
|
||||
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
|
||||
allChunks: true,
|
||||
}),
|
||||
// Compress extracted CSS. We are using this plugin so that possible
|
||||
// duplicated CSS from different components can be deduped.
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: config.build.productionSourceMap
|
||||
? { safe: true, map: { inline: false } }
|
||||
: { safe: true }
|
||||
}),
|
||||
// generate dist index.html with correct asset hash for caching.
|
||||
// you can customize output by editing /index.html
|
||||
// see https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: config.build.index,
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
},
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
}),
|
||||
// keep module.id stable when vender modules does not change
|
||||
new webpack.HashedModuleIdsPlugin(),
|
||||
// enable scope hoisting
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
// split vendor js into its own file
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks (module) {
|
||||
// any required modules inside node_modules are extracted to vendor
|
||||
return (
|
||||
module.resource &&
|
||||
/\.js$/.test(module.resource) &&
|
||||
module.resource.indexOf(
|
||||
path.join(__dirname, '../node_modules')
|
||||
) === 0
|
||||
)
|
||||
}
|
||||
}),
|
||||
// extract webpack runtime and module manifest to its own file in order to
|
||||
// prevent vendor hash from being updated whenever app bundle is updated
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'manifest',
|
||||
minChunks: Infinity
|
||||
}),
|
||||
// This instance extracts shared chunks from code splitted chunks and bundles them
|
||||
// in a separate chunk, similar to the vendor chunk
|
||||
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'app',
|
||||
async: 'vendor-async',
|
||||
children: true,
|
||||
minChunks: 3
|
||||
}),
|
||||
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
|
||||
if (config.build.productionGzip) {
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin')
|
||||
|
||||
webpackConfig.plugins.push(
|
||||
new CompressionWebpackPlugin({
|
||||
asset: '[path].gz[query]',
|
||||
algorithm: 'gzip',
|
||||
test: new RegExp(
|
||||
'\\.(' +
|
||||
config.build.productionGzipExtensions.join('|') +
|
||||
')$'
|
||||
),
|
||||
threshold: 10240,
|
||||
minRatio: 0.8
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (config.build.bundleAnalyzerReport) {
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
|
||||
}
|
||||
|
||||
module.exports = webpackConfig
|
|
@ -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']
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
'use strict'
|
||||
const merge = require('webpack-merge')
|
||||
const prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"'
|
||||
})
|
|
@ -1,85 +0,0 @@
|
|||
'use strict'
|
||||
// Template version: 1.2.7
|
||||
// see http://vuejs-templates.github.io/webpack for documentation.
|
||||
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
dev: {
|
||||
|
||||
// Paths
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {
|
||||
'/api':{
|
||||
target:'http://jsonplaceholder.typicode.com',
|
||||
changeOrigin:true,
|
||||
pathRewrite:{
|
||||
'/api':''
|
||||
}
|
||||
},
|
||||
'/ms':{
|
||||
target: 'https://www.easy-mock.com/mock/592501a391470c0ac1fab128',
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
// Various Dev Server settings
|
||||
host: 'localhost', // can be overwritten by process.env.HOST
|
||||
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
|
||||
autoOpenBrowser: false,
|
||||
errorOverlay: true,
|
||||
notifyOnErrors: true,
|
||||
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
|
||||
|
||||
|
||||
/**
|
||||
* Source Maps
|
||||
*/
|
||||
|
||||
// https://webpack.js.org/configuration/devtool/#development
|
||||
devtool: 'eval-source-map',
|
||||
|
||||
// If you have problems debugging vue-files in devtools,
|
||||
// set this to false - it *may* help
|
||||
// https://vue-loader.vuejs.org/en/options.html#cachebusting
|
||||
cacheBusting: true,
|
||||
|
||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
||||
// with this option, according to the CSS-Loader README
|
||||
// (https://github.com/webpack/css-loader#sourcemaps)
|
||||
// In our experience, they generally work as expected,
|
||||
// just be aware of this issue when enabling this option.
|
||||
cssSourceMap: false,
|
||||
},
|
||||
|
||||
build: {
|
||||
// Template for index.html
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
|
||||
// Paths
|
||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: './',
|
||||
|
||||
/**
|
||||
* Source Maps
|
||||
*/
|
||||
|
||||
productionSourceMap: false,
|
||||
// https://webpack.js.org/configuration/devtool/#production
|
||||
devtool: '#source-map',
|
||||
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
// Surge or Netlify already gzip all static assets for you.
|
||||
// Before setting to `true`, make sure to:
|
||||
// npm install --save-dev compression-webpack-plugin
|
||||
productionGzip: false,
|
||||
productionGzipExtensions: ['js', 'css'],
|
||||
|
||||
// Run the build command with an extra argument to
|
||||
// View the bundle analyzer report after build finishes:
|
||||
// `npm run build --report`
|
||||
// Set to `true` or `false` to always turn it on or off
|
||||
bundleAnalyzerReport: process.env.npm_config_report
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
'use strict'
|
||||
module.exports = {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
45
index.html
|
@ -1,23 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>vue-manage-system | 基于Vue 的后台管理系统</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
|
||||
<meta name="keywords" content="vue.js, wms, vue2, 后台模板, 管理系统, element" />
|
||||
<meta name="description" content="基于Vue2 + Element UI 的后台管理系统解决方案" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!--<script src="./static/js/vendor.dll.js"></script>-->
|
||||
<!-- <script>
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?1c343a080809502ac823823ae4c9ffe3";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script> -->
|
||||
</body>
|
||||
</html>
|
||||
<!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>
|
115
package.json
|
@ -1,71 +1,44 @@
|
|||
{
|
||||
"name": "vue-manage-system",
|
||||
"version": "3.0.0",
|
||||
"description": "基于Vue.js 2.x系列 + element-ui 内容管理系统解决方案",
|
||||
"author": "lin-xin <2981207131@qq.com>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
|
||||
"build": "node build/build.js",
|
||||
"build:dll": "webpack --config build/webpack.dll.conf.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.15.3",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"element-ui": "2.3.3",
|
||||
"mavon-editor": "^2.5.2",
|
||||
"vue": "^2.5.16",
|
||||
"vue-cropperjs": "^2.2.0",
|
||||
"vue-datasource": "1.0.12",
|
||||
"vue-quill-editor": "3.0.6",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-schart": "^0.1.4",
|
||||
"vuedraggable": "^2.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"babel-plugin-transform-runtime": "^6.22.0",
|
||||
"babel-plugin-transform-vue-jsx": "^3.5.0",
|
||||
"babel-preset-env": "^1.3.2",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-stage-2": "^6.22.0",
|
||||
"chalk": "^2.0.1",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"css-loader": "^0.28.0",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"file-loader": "^1.1.4",
|
||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"node-notifier": "^5.1.2",
|
||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||
"ora": "^1.2.0",
|
||||
"portfinder": "^1.0.13",
|
||||
"postcss-import": "^11.0.0",
|
||||
"postcss-loader": "^2.0.8",
|
||||
"rimraf": "^2.6.0",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"uglifyjs-webpack-plugin": "^1.1.1",
|
||||
"url-loader": "^0.5.8",
|
||||
"vue-loader": "^13.3.0",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
"vue-template-compiler": "^2.5.2",
|
||||
"webpack": "^3.6.0",
|
||||
"webpack-bundle-analyzer": "^2.9.0",
|
||||
"webpack-dev-server": "^2.9.1",
|
||||
"webpack-merge": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 5 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
}
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 121 KiB |
Before Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 425 KiB |
21
src/App.vue
|
@ -1,10 +1,17 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<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 "../static/css/main.css";
|
||||
@import "../static/css/color-dark.css"; /*深色主题*/
|
||||
/*@import "../static/css/theme-green/color-green.css"; 浅绿色主题*/
|
||||
</style>
|
||||
@import './assets/css/main.css';
|
||||
</style>
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
[class*=" el-icon-lx"],
|
||||
[class^=el-icon-lx] {
|
||||
font-family: lx-iconfont !important;
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
* {
|
||||
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);
|
||||
}
|
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 69 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144.08 128.61"><title>资源 82</title><path d="M72.23 128.61c-7.1-.23-11.51-3.72-14.76-9.36C48 102.87 38.43 86.59 29.1 70.16a36 36 0 0 1-4.47-11.35A14.61 14.61 0 0 1 34 42.51c7.49-2.71 15.71-.21 19.67 6.43 7.52 12.56 14.77 25.27 22.12 37.92 3 5.17 5.89 10.43 9 15.51 5 8 3.45 18-4.22 23.31-2.3 1.62-5.52 1.99-8.34 2.93z" fill="#2ef2e9"/><path d="M72.66.33c6-.57 10.39 2.6 13.51 8C95.61 24.69 105 41.1 114.52 57.4c3.9 6.65-.28 17.13-6.39 20.44-8.93 4.83-17.88 1.28-21.86-5.62C76.82 55.86 67.14 39.62 58.11 23 52.06 12 59.61.24 72.66.33z" fill="#fa6663"/><path d="M144.08 15.83c-.58 8.62-6.73 15.57-15.51 15.66-9.31.09-16.87-7-16.95-15.62S119 0 127.87 0c9.13.09 16.22 7 16.21 15.83z" fill="#fbb355"/><path d="M16.24 31.5C7 31.33-.19 24.42 0 15.8.19 7.5 7.19-.06 14.64 0c10.53.08 18.27 6.73 17.61 15.9-.64 8.96-6.25 15.28-16.01 15.6z" fill="#8a56c2"/></svg>
|
After Width: | Height: | Size: 918 B |
After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 6.7 KiB |
|
@ -1,97 +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="user-info">
|
||||
<el-dropdown trigger="click" @command="handleCommand">
|
||||
<span class="el-dropdown-link">
|
||||
<img class="user-logo" src="../../../static/img/img.jpg">
|
||||
{{username}}
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="loginout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import bus from '../common/bus';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
collapse: false,
|
||||
name: 'linxin'
|
||||
}
|
||||
},
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.header {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
font-size: 22px;
|
||||
line-height: 70px;
|
||||
color: #fff;
|
||||
}
|
||||
.collapse-btn{
|
||||
float: left;
|
||||
padding: 0 21px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.collapse-btn:hover{
|
||||
background: rgb(40,52,70);
|
||||
}
|
||||
.header .logo{
|
||||
float: left;
|
||||
width:250px;
|
||||
/* text-align: center; */
|
||||
}
|
||||
.user-info {
|
||||
float: right;
|
||||
padding-right: 50px;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
}
|
||||
.user-info .el-dropdown-link{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding-left: 50px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.user-info .user-logo{
|
||||
position: absolute;
|
||||
left:0;
|
||||
top:15px;
|
||||
width:40px;
|
||||
height:40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.el-dropdown-menu__item{
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -1,30 +0,0 @@
|
|||
<template>
|
||||
<div class="wrapper">
|
||||
<v-head></v-head>
|
||||
<v-sidebar></v-sidebar>
|
||||
<div class="content" :class="{'content-collapse':collapse}">
|
||||
<transition name="move" mode="out-in"><router-view></router-view></transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vHead from './Header.vue';
|
||||
import vSidebar from './Sidebar.vue';
|
||||
import bus from '../common/bus';
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
collapse: false
|
||||
}
|
||||
},
|
||||
components:{
|
||||
vHead, vSidebar
|
||||
},
|
||||
created(){
|
||||
bus.$on('collapse', msg => {
|
||||
this.collapse = msg;
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,122 +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>
|
||||
<el-menu-item v-for="(subItem,i) in item.subs" :key="i" :index="subItem.index">
|
||||
{{ subItem.title }}
|
||||
</el-menu-item>
|
||||
</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-setting',
|
||||
index: 'readme',
|
||||
title: '自述文件'
|
||||
},
|
||||
{
|
||||
icon: 'el-icon-tickets',
|
||||
index: '2',
|
||||
title: '常用表格',
|
||||
subs: [
|
||||
{
|
||||
index: 'table',
|
||||
title: '基础表格'
|
||||
},
|
||||
{
|
||||
index: 'datasource',
|
||||
title: 'datasource表格'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'el-icon-date',
|
||||
index: '3',
|
||||
title: '表单相关',
|
||||
subs: [
|
||||
{
|
||||
index: 'form',
|
||||
title: '基本表单'
|
||||
},
|
||||
{
|
||||
index: 'editor',
|
||||
title: '富文本编辑器'
|
||||
},
|
||||
{
|
||||
index: 'markdown',
|
||||
title: 'markdown编辑器'
|
||||
},
|
||||
{
|
||||
index: 'upload',
|
||||
title: '文件上传'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'el-icon-star-on',
|
||||
index: 'charts',
|
||||
title: 'schart图表'
|
||||
},
|
||||
{
|
||||
icon: 'el-icon-rank',
|
||||
index: 'drag',
|
||||
title: '拖拽列表'
|
||||
},
|
||||
{
|
||||
icon: 'el-icon-warning',
|
||||
index: 'permission',
|
||||
title: '权限测试'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
.sidebar-el-menu:not(.el-menu--collapse){
|
||||
width: 250px;
|
||||
}
|
||||
.sidebar > ul {
|
||||
height:100%;
|
||||
}
|
||||
</style>
|
|
@ -1,6 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
// 使用 Event Bus
|
||||
const bus = new Vue();
|
||||
|
||||
export default bus;
|
|
@ -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>
|
|
@ -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>
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
|
@ -1,88 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item><i class="el-icon-date"></i> 图表</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>基础图表</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
vue-schart:vue.js封装sChart.js的图表组件。
|
||||
访问地址:<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
|
||||
</div>
|
||||
<div class="schart">
|
||||
<div class="content-title">柱状图</div>
|
||||
<schart canvasId="bar" width="500" height="400" :data="data1" type="bar" :options="options1"></schart>
|
||||
</div>
|
||||
<div class="schart">
|
||||
<div class="content-title">折线图</div>
|
||||
<schart canvasId="line" width="500" height="400" :data="data1" type="line" :options="options1"></schart>
|
||||
</div>
|
||||
<div class="schart">
|
||||
<div class="content-title">饼状图</div>
|
||||
<schart canvasId="pie" width="500" height="400" :data="data2" type="pie" :options="options2"></schart>
|
||||
</div>
|
||||
<div class="schart">
|
||||
<div class="content-title">环形图</div>
|
||||
<schart canvasId="ring" width="500" height="400" :data="data2" type="ring" :options="options2"></schart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Schart from 'vue-schart';
|
||||
export default {
|
||||
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: '某商店近年营业总额',
|
||||
bgColor: '#009688',
|
||||
titleColor: '#ffffff',
|
||||
fillColor: '#e0f2f1',
|
||||
axisColor: '#ffffff',
|
||||
contentColor: '#999'
|
||||
},
|
||||
options2: {
|
||||
title: '某商店各商品年度销量',
|
||||
bgColor: '#607d8b',
|
||||
titleColor: '#ffffff',
|
||||
legendColor: '#ffffff'
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.schart{
|
||||
width: 600px;
|
||||
display: inline-block;
|
||||
}
|
||||
.content-title{
|
||||
clear: both;
|
||||
font-weight: 400;
|
||||
line-height: 50px;
|
||||
margin: 10px 0;
|
||||
font-size: 22px;
|
||||
color: #1f2f3d;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,140 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item><i class="el-icon-date"></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 {
|
||||
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>
|
|
@ -1,144 +0,0 @@
|
|||
<template>
|
||||
<div class="table">
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item><i class="el-icon-tickets"></i> 表格</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>基础表格</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 style="width: 100%" ref="multipleTable" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55"></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">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small"
|
||||
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger"
|
||||
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
@current-change ="handleCurrentChange"
|
||||
layout="prev, pager, next"
|
||||
:total="1000">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
url: './static/vuetable.json',
|
||||
tableData: [],
|
||||
cur_page: 1,
|
||||
multipleSelection: [],
|
||||
select_cate: '',
|
||||
select_word: '',
|
||||
del_list: [],
|
||||
is_search: false
|
||||
}
|
||||
},
|
||||
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.$message('编辑第'+(index+1)+'行');
|
||||
},
|
||||
handleDelete(index, row) {
|
||||
this.$message.error('删除第'+(index+1)+'行');
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.handle-box{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.handle-select{
|
||||
width: 120px;
|
||||
}
|
||||
.handle-input{
|
||||
width: 300px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
|
@ -1,163 +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>
|
||||
</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,index) in todo" class="drag-list" :key="index">
|
||||
{{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,index) in doing" class="drag-list" :key="index">
|
||||
{{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,index) in done" class="drag-list" :key="index">
|
||||
{{item.content}}
|
||||
</div>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dragOptions:{
|
||||
animation: 120,
|
||||
scroll: true,
|
||||
group: 'sortlist',
|
||||
ghostClass: 'ghost-style'
|
||||
},
|
||||
todo: [
|
||||
{
|
||||
content: '开发图表组件'
|
||||
},
|
||||
{
|
||||
content: '开发拖拽组件'
|
||||
},
|
||||
{
|
||||
content: '开发权限测试组件'
|
||||
}
|
||||
],
|
||||
doing: [
|
||||
{
|
||||
content: '开发登录注册页面'
|
||||
},
|
||||
{
|
||||
content: '开发头部组件'
|
||||
},
|
||||
{
|
||||
content: '开发表格相关组件'
|
||||
},
|
||||
{
|
||||
content: '开发表单相关组件'
|
||||
}
|
||||
],
|
||||
done:[
|
||||
{
|
||||
content: '初始化项目,生成工程目录,完成相关配置'
|
||||
},
|
||||
{
|
||||
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>
|
|
@ -1,89 +0,0 @@
|
|||
<template>
|
||||
<div class="login-wrap">
|
||||
<div class="ms-title">后台管理系统</div>
|
||||
<div class="ms-login">
|
||||
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="0px" class="demo-ruleForm">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="ruleForm.username" placeholder="username"></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-input>
|
||||
</el-form-item>
|
||||
<div class="login-btn">
|
||||
<el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
|
||||
</div>
|
||||
<p style="font-size:12px;line-height:30px;color:#999;">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('/readme');
|
||||
} else {
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-wrap{
|
||||
position: relative;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
.ms-title{
|
||||
position: absolute;
|
||||
top:50%;
|
||||
width:100%;
|
||||
margin-top: -230px;
|
||||
text-align: center;
|
||||
font-size:30px;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
.ms-login{
|
||||
position: absolute;
|
||||
left:50%;
|
||||
top:50%;
|
||||
width:300px;
|
||||
height:160px;
|
||||
margin:-150px 0 0 -190px;
|
||||
padding:40px;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
}
|
||||
.login-btn{
|
||||
text-align: center;
|
||||
}
|
||||
.login-btn button{
|
||||
width:100%;
|
||||
height:36px;
|
||||
}
|
||||
</style>
|
|
@ -1,68 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item><i class="el-icon-date"></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'
|
||||
// 重写mavon的flex样式来兼容IE
|
||||
import 'static/css/mavon-flex.css'
|
||||
export default {
|
||||
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>
|
|
@ -1,38 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item><i class="el-icon-warning"></i> 权限测试</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h1>管理员权限页面</h1>
|
||||
<p>只有用 admin 账号登录的才拥有管理员权限,才能进到这个页面,其他账号想进来都会跳到登录页面,重新用管理员账号登录才有权限。</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>
|
|
@ -1,88 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item><i class="el-icon-setting"></i> 自述</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
<div class="ms-doc">
|
||||
<h3>README.md</h3>
|
||||
<article>
|
||||
<h1>manage-system</h1>
|
||||
<p>基于Vue.js 2.x系列 + Element UI 的后台管理系统解决方案</p>
|
||||
<h2>前言</h2>
|
||||
<p>之前在公司用了Vue + Element组件库做了个后台管理系统,基本很多组件可以直接引用组件库的,但是也有一些需求无法满足。像图片裁剪上传、富文本编辑器、图表等这些在后台管理系统中很常见的功能,就需要引用其他的组件才能完成。从寻找组件,到使用组件的过程中,遇到了很多问题,也积累了宝贵的经验。所以我就把开发这个后台管理系统的经验,总结成这个后台管理系统解决方案。</p>
|
||||
<p>该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统(Web Management System)开发。基于vue.js,使用vue-cli脚手架快速生成项目目录,引用Element UI组件库,方便开发快速简洁好看的组件。分离颜色样式,支持手动切换主题色,而且很方便使用自定义主题色。</p>
|
||||
<h2>功能</h2>
|
||||
<el-checkbox disabled checked>Element UI</el-checkbox>
|
||||
<br>
|
||||
<el-checkbox disabled checked>登录/注销</el-checkbox>
|
||||
<br>
|
||||
<el-checkbox disabled checked>表格</el-checkbox>
|
||||
<br>
|
||||
<el-checkbox disabled checked>表单</el-checkbox>
|
||||
<br>
|
||||
<el-checkbox disabled checked>图表</el-checkbox>
|
||||
<br>
|
||||
<el-checkbox disabled checked>富文本编辑器</el-checkbox>
|
||||
<br>
|
||||
<el-checkbox disabled checked>markdown编辑器</el-checkbox>
|
||||
<br>
|
||||
<el-checkbox disabled checked>图片拖拽/裁剪上传</el-checkbox>
|
||||
<br>
|
||||
<el-checkbox disabled checked>支持切换主题色</el-checkbox>
|
||||
<br>
|
||||
<el-checkbox disabled checked>列表拖拽排序</el-checkbox>
|
||||
<br>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ms-doc{
|
||||
width:100%;
|
||||
max-width: 980px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.ms-doc h3{
|
||||
padding: 9px 10px 10px;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #d8d8d8;
|
||||
border-bottom: 0;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
.ms-doc article{
|
||||
padding: 45px;
|
||||
word-wrap: break-word;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
.ms-doc article h1{
|
||||
font-size:32px;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.ms-doc article h2 {
|
||||
margin: 24px 0 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
padding-bottom: 7px;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.ms-doc article p{
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.ms-doc article .el-checkbox{
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
|
@ -1,140 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item><i class="el-icon-date"></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 {
|
||||
data: function(){
|
||||
return {
|
||||
defaultSrc: './static/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>
|
|
@ -1,52 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item><i class="el-icon-date"></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 {
|
||||
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>
|
|
@ -1,97 +0,0 @@
|
|||
<template>
|
||||
<div class="table">
|
||||
<div class="crumbs">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item><i class="el-icon-tickets"></i> 表格</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>VueDatasource</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
vue-datasource:一个用于动态创建表格的vue.js服务端组件。
|
||||
访问地址:<a href="https://github.com/coderdiaz/vue-datasource" target="_blank">vue-datasource</a>
|
||||
</div>
|
||||
<datasource language="en" :table-data="getData" :columns="columns" :pagination="information.pagination"
|
||||
:actions="actions"
|
||||
v-on:change="changePage"
|
||||
v-on:searching="onSearch"
|
||||
></datasource>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import Datasource from 'vue-datasource';
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
url: './static/datasource.json',
|
||||
information: {
|
||||
pagination:{},
|
||||
data:[]
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
name: 'Id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
name: 'Name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
name: 'ip',
|
||||
key: 'ip',
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
text: 'Click',
|
||||
class: 'btn-primary',
|
||||
event: (e, row)=>{
|
||||
row && this.$message('选中的行数: ' + row.row.id);
|
||||
}
|
||||
}
|
||||
],
|
||||
query:''
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Datasource
|
||||
},
|
||||
methods: {
|
||||
changePage(values) {
|
||||
this.information.pagination.per_page = values.perpage;
|
||||
this.information.data = this.information.data;
|
||||
},
|
||||
onSearch(searchQuery) {
|
||||
this.query = searchQuery;
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
getData(){
|
||||
return this.information.data.filter((d) =>{
|
||||
if(d.name.indexOf(this.query) > -1){
|
||||
return d;
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
beforeMount(){
|
||||
// 开发环境使用 easy-mock 数据,正式环境使用 json 文件
|
||||
if(process.env.NODE_ENV === 'development'){
|
||||
this.url = '/ms/table/source';
|
||||
};
|
||||
axios.get(this.url).then( (res) => {
|
||||
this.information = res.data;
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style src="../../../static/css/datasource.css"></style>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div class="search-container">
|
||||
<el-form ref="searchRef" :model="query" :inline="true">
|
||||
<el-form-item :label="item.label" :prop="item.prop" v-for="item in options">
|
||||
<!-- 文本框、下拉框、日期框 -->
|
||||
<el-input v-if="item.type === 'input'" v-model="query[item.prop]" :disabled="item.disabled"
|
||||
:placeholder="item.placeholder" clearable></el-input>
|
||||
<el-select v-else-if="item.type === 'select'" v-model="query[item.prop]" :disabled="item.disabled"
|
||||
:placeholder="item.placeholder" clearable>
|
||||
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option>
|
||||
</el-select>
|
||||
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="query[item.prop]"
|
||||
:value-format="item.format"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :icon="Search" @click="search">搜索</el-button>
|
||||
<el-button :icon="Refresh" @click="resetForm(searchRef)">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { Search, Refresh } from '@element-plus/icons-vue';
|
||||
import { PropType, ref } from 'vue';
|
||||
import { FormOptionList } from '@/types/form-option';
|
||||
|
||||
const props = defineProps({
|
||||
query: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<Array<FormOptionList>>,
|
||||
required: true
|
||||
},
|
||||
search: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
}
|
||||
});
|
||||
|
||||
const searchRef = ref<FormInstance>();
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
props.search();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-container {
|
||||
padding: 20px 30px 0;
|
||||
background-color: #fff;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,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>
|
34
src/main.js
|
@ -1,34 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import App from './App';
|
||||
import router from './router';
|
||||
import axios from 'axios';
|
||||
import ElementUI from 'element-ui';
|
||||
import 'element-ui/lib/theme-chalk/index.css'; // 默认主题
|
||||
// import '../static/css/theme-green/index.css'; // 浅绿色主题
|
||||
import "babel-polyfill";
|
||||
|
||||
Vue.use(ElementUI, { size: 'small' });
|
||||
Vue.prototype.$axios = axios;
|
||||
|
||||
//使用钩子函数对路由进行权限跳转
|
||||
router.beforeEach((to, from, next) => {
|
||||
if(to.meta.permission){
|
||||
const role = localStorage.getItem('ms_username');
|
||||
// 如果是管理员权限则可进入,这里只是简单的模拟管理员权限而已
|
||||
role === 'admin' ? next() : next('/login');
|
||||
}else{
|
||||
// 简单的判断IE10及以下不进入富文本编辑器,该组件不兼容
|
||||
if(navigator.userAgent.indexOf('MSIE') > -1 && to.path === '/editor'){
|
||||
Vue.prototype.$alert('vue-quill-editor组件不兼容IE10及以下浏览器,请使用更高版本的浏览器查看', '浏览器不兼容通知', {
|
||||
confirmButtonText: '确定'
|
||||
});
|
||||
}else{
|
||||
next();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app');
|
|
@ -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');
|
|
@ -1,71 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
export default new Router({
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/login'
|
||||
},
|
||||
{
|
||||
path: '/readme',
|
||||
component: resolve => require(['../components/common/Home.vue'], resolve),
|
||||
children:[
|
||||
{
|
||||
path: '/',
|
||||
component: resolve => require(['../components/page/Readme.vue'], resolve)
|
||||
},
|
||||
{
|
||||
path: '/table',
|
||||
component: resolve => require(['../components/page/BaseTable.vue'], resolve)
|
||||
},
|
||||
{
|
||||
// vue-datasource组件
|
||||
path: '/datasource',
|
||||
component: resolve => require(['../components/page/VueTable.vue'], resolve)
|
||||
},
|
||||
{
|
||||
path: '/form',
|
||||
component: resolve => require(['../components/page/BaseForm.vue'], resolve)
|
||||
},
|
||||
{
|
||||
// 富文本编辑器组件
|
||||
path: '/editor',
|
||||
component: resolve => require(['../components/page/VueEditor.vue'], resolve)
|
||||
},
|
||||
{
|
||||
// markdown组件
|
||||
path: '/markdown',
|
||||
component: resolve => require(['../components/page/Markdown.vue'], resolve)
|
||||
},
|
||||
{
|
||||
// 图片上传组件
|
||||
path: '/upload',
|
||||
component: resolve => require(['../components/page/Upload.vue'], resolve)
|
||||
},
|
||||
{
|
||||
// vue-schart组件
|
||||
path: '/charts',
|
||||
component: resolve => require(['../components/page/BaseCharts.vue'], resolve)
|
||||
},
|
||||
{
|
||||
// 拖拽列表组件
|
||||
path: '/drag',
|
||||
component: resolve => require(['../components/page/DragList.vue'], resolve)
|
||||
},
|
||||
{
|
||||
// 权限页面
|
||||
path: '/permission',
|
||||
component: resolve => require(['../components/page/Permission.vue'], resolve),
|
||||
meta: {permission: true}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: resolve => require(['../components/page/Login.vue'], resolve)
|
||||
},
|
||||
]
|
||||
})
|
|
@ -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;
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
import { mix, setProperty } from '@/utils';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useThemeStore = defineStore('theme', {
|
||||
state: () => {
|
||||
return {
|
||||
primary: '',
|
||||
success: '',
|
||||
warning: '',
|
||||
danger: '',
|
||||
info: '',
|
||||
headerBgColor: '#242f42',
|
||||
headerTextColor: '#fff',
|
||||
};
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
initTheme() {
|
||||
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
|
||||
const color = localStorage.getItem(`theme-${type}`) || '';
|
||||
if (color) {
|
||||
this.setPropertyColor(color, type); // 设置主题色
|
||||
}
|
||||
});
|
||||
const headerBgColor = localStorage.getItem('header-bg-color');
|
||||
headerBgColor && this.setHeaderBgColor(headerBgColor);
|
||||
const headerTextColor = localStorage.getItem('header-text-color');
|
||||
headerTextColor && this.setHeaderTextColor(headerTextColor);
|
||||
},
|
||||
resetTheme() {
|
||||
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
|
||||
this.setPropertyColor('', type); // 重置主题色
|
||||
});
|
||||
},
|
||||
setPropertyColor(color: string, type: string = 'primary') {
|
||||
this[type] = color;
|
||||
setProperty(`--el-color-${type}`, color);
|
||||
localStorage.setItem(`theme-${type}`, color);
|
||||
this.setThemeLight(type);
|
||||
},
|
||||
setThemeLight(type: string = 'primary') {
|
||||
[3, 5, 7, 8, 9].forEach((v) => {
|
||||
setProperty(`--el-color-${type}-light-${v}`, mix('#ffffff', this[type], v / 10));
|
||||
});
|
||||
setProperty(`--el-color-${type}-dark-2`, mix('#ffffff', this[type], 0.2));
|
||||
},
|
||||
setHeaderBgColor(color: string) {
|
||||
this.headerBgColor = color;
|
||||
setProperty('--header-bg-color', color);
|
||||
localStorage.setItem(`header-bg-color`, color);
|
||||
},
|
||||
setHeaderTextColor(color: string) {
|
||||
this.headerTextColor = color;
|
||||
setProperty('--header-text-color', color);
|
||||
localStorage.setItem(`header-text-color`, color);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
export interface FormOption {
|
||||
list: FormOptionList[];
|
||||
labelWidth?: number | string;
|
||||
span?: number;
|
||||
|
||||
}
|
||||
|
||||
export interface FormOptionList {
|
||||
prop: string;
|
||||
label: string;
|
||||
type: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
opts?: any[];
|
||||
format?: string;
|
||||
activeValue?: any;
|
||||
inactiveValue?: any;
|
||||
activeText?: string;
|
||||
inactiveText?: string;
|
||||
required?: boolean;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export interface Menus {
|
||||
id: string;
|
||||
pid?: string;
|
||||
icon?: string;
|
||||
index: string;
|
||||
title: string;
|
||||
permiss?: string;
|
||||
children?: Menus[];
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
export interface Role {
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
status: boolean;
|
||||
permiss: string[]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export interface TableItem {
|
||||
id: number;
|
||||
name: string;
|
||||
thumb: string;
|
||||
money: number;
|
||||
state: string;
|
||||
date: string;
|
||||
address: string;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
export interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
password: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
role: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export interface Register {
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
vue-echarts:Apache ECharts™ 的 Vue.js 组件。 访问地址:
|
||||
<a href="https://github.com/ecomfe/vue-echarts" target="_blank">vue-echarts</a>
|
||||
</div>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">柱状图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="barOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">折线图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="lineOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">饼状图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="pieOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">环形图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="ringOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">词云图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="wordOptions" />
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>
|
||||
<div class="content-title">地图</div>
|
||||
</template>
|
||||
<v-chart class="schart" :option="mapOptions" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="echarts">
|
||||
import { registerMap, use } from 'echarts/core';
|
||||
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
VisualMapComponent,
|
||||
} from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import VChart from 'vue-echarts';
|
||||
import 'echarts-wordcloud';
|
||||
import { barOptions, lineOptions, pieOptions, ringOptions, wordOptions, mapOptions } from './options';
|
||||
import chinaMap from '@/utils/china';
|
||||
use([
|
||||
CanvasRenderer,
|
||||
BarChart,
|
||||
GridComponent,
|
||||
LineChart,
|
||||
PieChart,
|
||||
MapChart,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
VisualMapComponent,
|
||||
]);
|
||||
registerMap('china', chinaMap);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.schart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.content-title {
|
||||
font-weight: 400;
|
||||
font-size: 22px;
|
||||
color: #1f2f3d;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,345 @@
|
|||
import { graphic } from 'echarts/core';
|
||||
export const barOptions = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
color: ['#009688', '#f44336'],
|
||||
series: [
|
||||
{
|
||||
data: [120, 200, 150, 80, 70, 110, 130],
|
||||
type: 'bar',
|
||||
},
|
||||
{
|
||||
data: [180, 230, 190, 120, 110, 230, 235],
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const lineOptions = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
color: ['#009688', '#f44336'],
|
||||
series: [
|
||||
{
|
||||
name: 'Email',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
areaStyle: {},
|
||||
smooth: true,
|
||||
data: [120, 132, 101, 134, 90, 230, 210],
|
||||
},
|
||||
{
|
||||
name: 'Union Ads',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
areaStyle: {},
|
||||
smooth: true,
|
||||
data: [220, 182, 191, 234, 290, 330, 310],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const pieOptions = {
|
||||
title: {
|
||||
text: 'Referer of a Website',
|
||||
subtext: 'Fake Data',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Access From',
|
||||
type: 'pie',
|
||||
radius: '50%',
|
||||
data: [
|
||||
{ value: 1048, name: 'Search Engine' },
|
||||
{ value: 735, name: 'Direct' },
|
||||
{ value: 580, name: 'Email' },
|
||||
{ value: 484, name: 'Union Ads' },
|
||||
{ value: 300, name: 'Video Ads' },
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const wordOptions = {
|
||||
series: [
|
||||
{
|
||||
type: 'wordCloud',
|
||||
rotationRange: [0, 0],
|
||||
autoSize: {
|
||||
enable: true,
|
||||
minSize: 14,
|
||||
},
|
||||
textStyle: {
|
||||
fontFamily: '微软雅黑,sans-serif',
|
||||
color: function () {
|
||||
return (
|
||||
'rgb(' +
|
||||
[
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160),
|
||||
].join(',') +
|
||||
')'
|
||||
);
|
||||
},
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: 'Vue',
|
||||
value: 10000,
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
value: 9000,
|
||||
},
|
||||
{
|
||||
name: '图表',
|
||||
value: 4000,
|
||||
},
|
||||
{
|
||||
name: '产品',
|
||||
value: 7000,
|
||||
},
|
||||
{
|
||||
name: 'vue-manage-system',
|
||||
value: 2000,
|
||||
},
|
||||
{
|
||||
name: 'element-plus',
|
||||
value: 6000,
|
||||
},
|
||||
{
|
||||
name: '管理系统',
|
||||
value: 5000,
|
||||
},
|
||||
{
|
||||
name: '前端',
|
||||
value: 4000,
|
||||
},
|
||||
{
|
||||
name: '测试',
|
||||
value: 3000,
|
||||
},
|
||||
{
|
||||
name: '后端',
|
||||
value: 8000,
|
||||
},
|
||||
{
|
||||
name: '软件开发',
|
||||
value: 6000,
|
||||
},
|
||||
{
|
||||
name: '程序员',
|
||||
value: 4000,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const ringOptions = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
top: '5%',
|
||||
left: 'center',
|
||||
},
|
||||
|
||||
series: [
|
||||
{
|
||||
name: 'Access From',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: [
|
||||
{ value: 1048, name: 'Search Engine' },
|
||||
{ value: 735, name: 'Direct' },
|
||||
{ value: 580, name: 'Email' },
|
||||
{ value: 484, name: 'Union Ads' },
|
||||
{ value: 300, name: 'Video Ads' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const dashOpt1 = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
grid: {
|
||||
top: '2%',
|
||||
left: '2%',
|
||||
right: '3%',
|
||||
bottom: '2%',
|
||||
containLabel: true,
|
||||
},
|
||||
color: ['#009688', '#f44336'],
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(0, 150, 136,0.8)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(0, 150, 136,0.2)',
|
||||
},
|
||||
]),
|
||||
},
|
||||
smooth: true,
|
||||
data: [120, 132, 301, 134, 90, 230, 210],
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: [220, 122, 191, 234, 190, 130, 310],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const dashOpt2 = {
|
||||
legend: {
|
||||
bottom: '1%',
|
||||
left: 'center',
|
||||
},
|
||||
color: ['#3f51b5', '#009688', '#f44336', '#00bcd4', '#1ABC9C'],
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
data: [
|
||||
{ value: 1048, name: '数码' },
|
||||
{ value: 735, name: '食品' },
|
||||
{ value: 580, name: '母婴' },
|
||||
{ value: 484, name: '家电' },
|
||||
{ value: 300, name: '运动' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mapOptions = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
geo: {
|
||||
map: 'china',
|
||||
roam: false,
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
visualMap: {
|
||||
show: false,
|
||||
min: 0,
|
||||
max: 100,
|
||||
realtime: false,
|
||||
calculable: false,
|
||||
inRange: {
|
||||
color: ['#d2e0f5', '#71A9FF'],
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
geoIndex: 0,
|
||||
name: '地域分布',
|
||||
type: 'map',
|
||||
coordinateSystem: 'geo',
|
||||
map: 'china',
|
||||
data: [
|
||||
{ name: '北京', value: 100 },
|
||||
{ name: '上海', value: 100 },
|
||||
{ name: '广东', value: 100 },
|
||||
{ name: '浙江', value: 90 },
|
||||
{ name: '江西', value: 80 },
|
||||
{ name: '山东', value: 70 },
|
||||
{ name: '广西', value: 60 },
|
||||
{ name: '河南', value: 50 },
|
||||
{ name: '河南', value: 40 },
|
||||
{ name: '青海', value: 70 },
|
||||
{ name: '河南', value: 30 },
|
||||
{ name: '黑龙江', value: 20 },
|
||||
{ name: '新疆', value: 20 },
|
||||
{ name: '云南', value: 20 },
|
||||
{ name: '甘肃', value: 20 },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
vue-schart:vue.js封装sChart.js的图表组件。 访问地址:
|
||||
<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
|
||||
</div>
|
||||
<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>
|
|
@ -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>
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<el-calendar v-model="value">
|
||||
<template #date-cell="{ data }">
|
||||
<div>{{ data.date.getDate() }}</div>
|
||||
<div class="notes-container" v-if="notes[data.day.toString()]">
|
||||
<div class="notes" v-for="note in notes[data.day.toString()]">
|
||||
<span :class="note.status === 1 ? 'text-success' : 'text-danger'"></span>
|
||||
<div class="note-title">{{ note.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-calendar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const today = new Date();
|
||||
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
||||
const value = ref(today);
|
||||
|
||||
const todayDate = today.toISOString().slice(0, 10);
|
||||
const yesterdayDate = yesterday.toISOString().slice(0, 10);
|
||||
|
||||
const notes: any = {
|
||||
[todayDate]: [
|
||||
{ title: '吃饭', status: 1 },
|
||||
{ title: '睡觉', status: 0 },
|
||||
{ title: '吃饭', status: 1 },
|
||||
{ title: '睡觉', status: 0 },
|
||||
{ title: '吃饭', status: 1 },
|
||||
{ title: '睡觉', status: 0 },
|
||||
],
|
||||
[yesterdayDate]: [{ title: '参加会议', status: 0 }],
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.notes-container {
|
||||
height: 60px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.notes-container::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.notes {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.notes:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.note-title {
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notes span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
background-color: #5cb85c;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
background-color: #d9534f;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card class="mgb20">
|
||||
<template #header>基础用法</template>
|
||||
<el-carousel height="400px">
|
||||
<el-carousel-item v-for="item in 4" :key="item">
|
||||
<h3>{{ item }}</h3>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card class="mgb20">
|
||||
<template #header>轮播图</template>
|
||||
<el-carousel height="300px">
|
||||
<el-carousel-item v-for="item in imgs" :key="item">
|
||||
<el-image class="carousel-img" :src="item" fit="cover" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card class="mgb20">
|
||||
<template #header>卡片模式</template>
|
||||
<el-carousel height="300px" type="card">
|
||||
<el-carousel-item v-for="item in imgs" :key="item">
|
||||
<el-image class="carousel-img" :src="item" fit="cover" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const imgs = [
|
||||
'https://cdn.pixabay.com/photo/2017/08/07/08/23/sea-2601374_640.jpg',
|
||||
'https://cdn.pixabay.com/photo/2020/02/11/10/24/lake-4839058_640.jpg',
|
||||
'https://cdn.pixabay.com/photo/2024/02/21/08/06/coast-8587004_640.jpg',
|
||||
'https://cdn.pixabay.com/photo/2023/07/29/10/21/grasshopper-8156626_640.jpg',
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-carousel__item h3 {
|
||||
color: #475669;
|
||||
line-height: 400px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n) {
|
||||
background-color: #99a9bf;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n + 1) {
|
||||
background-color: #d3dce6;
|
||||
}
|
||||
|
||||
.carousel-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,189 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<el-radio-group class="mgb20" v-model="labelPosition">
|
||||
<el-radio-button value="left">Left</el-radio-button>
|
||||
<el-radio-button value="right">Right</el-radio-button>
|
||||
<el-radio-button value="top">Top</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="120px" :label-position="labelPosition">
|
||||
<el-row :gutter="50">
|
||||
<el-col :span="10">
|
||||
<el-form-item label="文本框" prop="name">
|
||||
<el-input v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="数字框" prop="num">
|
||||
<el-input-number v-model="form.num" :min="1" :max="10" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日期选择" prop="date">
|
||||
<el-date-picker type="date" placeholder="选择日期" v-model="form.date"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间选择" prop="time">
|
||||
<el-time-picker placeholder="选择时间" v-model="form.time">
|
||||
</el-time-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择器" prop="region">
|
||||
<el-select v-model="form.region" placeholder="请选择">
|
||||
<el-option key="小明" label="小明" value="小明"></el-option>
|
||||
<el-option key="小红" label="小红" value="小红"></el-option>
|
||||
<el-option key="小白" label="小白" value="小白"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="城市级联" prop="options">
|
||||
<el-cascader :options="options" v-model="form.options"></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="文本框" prop="desc">
|
||||
<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="评分" prop="rate">
|
||||
<el-rate v-model="form.rate" allow-half />
|
||||
</el-form-item>
|
||||
<el-form-item label="滑块" prop="num">
|
||||
<el-slider v-model="form.num" :step="1" show-stops :max="10" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开关" prop="delivery">
|
||||
<el-switch v-model="form.delivery"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="颜色选择" prop="color">
|
||||
<el-color-picker v-model="form.color" />
|
||||
</el-form-item>
|
||||
<el-form-item label="多选框" prop="type">
|
||||
<el-checkbox-group v-model="form.type">
|
||||
<el-checkbox label="小明" value="小明" name="type"></el-checkbox>
|
||||
<el-checkbox label="小红" value="小红" name="type"></el-checkbox>
|
||||
<el-checkbox label="小白" value="小白" name="type"></el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="单选框" prop="resource">
|
||||
<el-radio-group v-model="form.resource">
|
||||
<el-radio label="小明" value="小明"></el-radio>
|
||||
<el-radio label="小红" value="小红"></el-radio>
|
||||
<el-radio label="小白" value="小白"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="穿梭框" prop="transfer">
|
||||
<el-transfer v-model="form.transfer" :data="transferData" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">表单提交</el-button>
|
||||
<el-button @click="onReset(formRef)">重置表单</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="forms">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance, FormProps, FormRules } from 'element-plus';
|
||||
const labelPosition = ref<FormProps['labelPosition']>('right')
|
||||
const options = [
|
||||
{
|
||||
value: 'guangdong',
|
||||
label: '广东省',
|
||||
children: [
|
||||
{
|
||||
value: 'guangzhou',
|
||||
label: '广州市',
|
||||
children: [
|
||||
{
|
||||
value: 'tianhe',
|
||||
label: '天河区',
|
||||
},
|
||||
{
|
||||
value: 'haizhu',
|
||||
label: '海珠区',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'dongguan',
|
||||
label: '东莞市',
|
||||
children: [
|
||||
{
|
||||
value: 'changan',
|
||||
label: '长安镇',
|
||||
},
|
||||
{
|
||||
value: 'humen',
|
||||
label: '虎门镇',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'hunan',
|
||||
label: '湖南省',
|
||||
children: [
|
||||
{
|
||||
value: 'changsha',
|
||||
label: '长沙市',
|
||||
children: [
|
||||
{
|
||||
value: 'yuelu',
|
||||
label: '岳麓区',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const rules: FormRules = {
|
||||
name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }],
|
||||
};
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive({
|
||||
name: '',
|
||||
region: '',
|
||||
date: '',
|
||||
time: '',
|
||||
delivery: true,
|
||||
type: ['小明'],
|
||||
resource: '小红',
|
||||
desc: '',
|
||||
options: [],
|
||||
color: '',
|
||||
num: 1,
|
||||
rate: 0,
|
||||
transfer: [],
|
||||
|
||||
});
|
||||
const generateData = () => {
|
||||
const data = []
|
||||
for (let i = 1; i <= 15; i++) {
|
||||
data.push({
|
||||
key: i,
|
||||
label: `Option ${i}`,
|
||||
disabled: i % 4 === 0,
|
||||
})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const transferData = ref(generateData())
|
||||
// 提交
|
||||
const onSubmit = (formEl: FormInstance | undefined) => {
|
||||
// 表单校验
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
console.log(form);
|
||||
ElMessage.success('提交成功!');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
// 重置
|
||||
const onReset = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,340 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>基础用法</template>
|
||||
<el-row>
|
||||
<el-col :span="6" style="text-align: center">
|
||||
<el-statistic title="Daily active users" :value="268500" />
|
||||
</el-col>
|
||||
<el-col :span="6" style="text-align: center">
|
||||
<el-statistic :value="138">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
Ratio of men to women
|
||||
</div>
|
||||
</template>
|
||||
<template #suffix>/100</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="6" style="text-align: center">
|
||||
<el-statistic title="数字滚动" :value="outputValue" />
|
||||
</el-col>
|
||||
<el-col :span="6" style="text-align: center">
|
||||
<el-countdown title="倒计时" :value="value" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mgb20" shadow="hover">
|
||||
<template #header>CountUp.js</template>
|
||||
<div class="plugins-tips">
|
||||
countup.js:用于快速创建以更有趣的方式显示数字数据的动画。 访问地址:
|
||||
<a href="https://github.com/inorganik/countUp.js" target="_blank">countUp.js</a>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="8" style="text-align: center">
|
||||
<p>基础用法</p>
|
||||
<countup class="countup" :end="6666" />
|
||||
</el-col>
|
||||
<el-col :span="8" style="text-align: center">
|
||||
<p>具体配置</p>
|
||||
<countup class="countup" :end="8888.5" :options="options" />
|
||||
</el-col>
|
||||
<el-col :span="8" style="text-align: center">
|
||||
<p>更新数值</p>
|
||||
<countup class="countup" :end="value1" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<el-card class="mgb20" shadow="never">
|
||||
<template #header>统计卡片</template>
|
||||
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon color1">
|
||||
<User />
|
||||
</el-icon>
|
||||
<div class="card-content text-right">
|
||||
<el-statistic title="日活跃用户量" :value="268500" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon color2">
|
||||
<ChatDotRound />
|
||||
</el-icon>
|
||||
<div class="card-content text-right">
|
||||
<el-statistic title="系统消息" :value="16800" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon color3">
|
||||
<Goods />
|
||||
</el-icon>
|
||||
<div class="card-content text-right">
|
||||
<el-statistic title="商品数量" :value="8888" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon color4">
|
||||
<ShoppingCartFull />
|
||||
</el-icon>
|
||||
<div class="card-content text-right">
|
||||
<el-statistic title="今日订单量" :value="56888" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<div class="card-content text-left">
|
||||
<el-statistic :value-style="{ color: '#2d8cf0' }" title="日活跃用户量" :value="268500" />
|
||||
</div>
|
||||
<el-icon class="card-icon color1">
|
||||
<User />
|
||||
</el-icon>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<div class="card-content text-left">
|
||||
<el-statistic :value-style="{ color: '#64d572' }" title="系统消息" :value="16800" />
|
||||
</div>
|
||||
<el-icon class="card-icon color2">
|
||||
<ChatDotRound />
|
||||
</el-icon>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<div class="card-content text-left">
|
||||
<el-statistic :value-style="{ color: '#f25e43' }" title="商品数量" :value="8888" />
|
||||
</div>
|
||||
<el-icon class="card-icon color3">
|
||||
<Goods />
|
||||
</el-icon>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<div class="card-content text-left">
|
||||
<el-statistic :value-style="{ color: '#e9a745' }" title="今日订单量" :value="56888" />
|
||||
</div>
|
||||
<el-icon class="card-icon color4">
|
||||
<ShoppingCartFull />
|
||||
</el-icon>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg1">
|
||||
<User />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color1" :end="6666" />
|
||||
<div>用户访问量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg2">
|
||||
<ChatDotRound />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color2" :end="168" />
|
||||
<div>系统消息</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg3">
|
||||
<Goods />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color3" :end="8888" />
|
||||
<div>商品数量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body">
|
||||
<el-icon class="card-icon bg4">
|
||||
<ShoppingCartFull />
|
||||
</el-icon>
|
||||
<div class="card-content">
|
||||
<countup class="card-num color4" :end="568" />
|
||||
<div>今日订单量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body bg1">
|
||||
<el-icon class="card-icon ">
|
||||
<User />
|
||||
</el-icon>
|
||||
<div class="card-content color0">
|
||||
<countup class="card-num" :end="6666" />
|
||||
<div>用户访问量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body bg2">
|
||||
<el-icon class="card-icon">
|
||||
<ChatDotRound />
|
||||
</el-icon>
|
||||
<div class="card-content color0">
|
||||
<countup class="card-num" :end="168" />
|
||||
<div>系统消息</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body bg3">
|
||||
<el-icon class="card-icon">
|
||||
<Goods />
|
||||
</el-icon>
|
||||
<div class="card-content color0">
|
||||
<countup class="card-num " :end="8888" />
|
||||
<div>商品数量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" body-class="card-body bg4">
|
||||
<el-icon class="card-icon">
|
||||
<ShoppingCartFull />
|
||||
</el-icon>
|
||||
<div class="card-content color0">
|
||||
<countup class="card-num " :end="568" />
|
||||
<div>今日订单量</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { useTransition } from '@vueuse/core'
|
||||
import countup from '@/components/countup.vue';
|
||||
|
||||
const source = ref(0)
|
||||
const outputValue = useTransition(source, {
|
||||
duration: 1500,
|
||||
})
|
||||
source.value = 172000
|
||||
|
||||
const value = ref(Date.now() + 1000 * 60 * 60 * 7)
|
||||
const value1 = ref(1000);
|
||||
setTimeout(() => {
|
||||
value1.value = 8000;
|
||||
}, 5000);
|
||||
const options = {
|
||||
startVal: 1000,
|
||||
decimalPlaces: 2,
|
||||
duration: 5,
|
||||
useGrouping: false,
|
||||
prefix: '$',
|
||||
separator: ',',
|
||||
decimal: '.',
|
||||
suffix: '',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bg1 {
|
||||
background: #2d8cf0;
|
||||
}
|
||||
|
||||
.bg2 {
|
||||
background: #64d572;
|
||||
}
|
||||
|
||||
.bg3 {
|
||||
background: #f25e43;
|
||||
}
|
||||
|
||||
.bg4 {
|
||||
background: #e9a745;
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
.countup {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.card-num {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 50px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.color0 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.color1 {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
|
||||
.color2 {
|
||||
color: #64d572;
|
||||
}
|
||||
|
||||
.color3 {
|
||||
color: #f25e43;
|
||||
}
|
||||
|
||||
.color4 {
|
||||
color: #e9a745;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="step-div" v-if="step === 0">
|
||||
<p>输入注册时的邮箱,我们会发送验证码到您的邮箱</p>
|
||||
<el-input placeholder="请输入邮箱"></el-input>
|
||||
<el-button class="step-btn" type="primary" @click="step++">下一步</el-button>
|
||||
</div>
|
||||
<div class="step-div" v-else-if="step === 1">
|
||||
<p>验证码已发送至您的邮箱,请输入验证码</p>
|
||||
<el-input placeholder="请输入验证码"></el-input>
|
||||
<el-button class="step-btn" type="primary" @click="step++">下一步</el-button>
|
||||
</div>
|
||||
|
||||
<div class="step-div" v-else-if="step === 2">
|
||||
<p>请输入6位以上密码</p>
|
||||
<el-input placeholder="请输入新密码"></el-input>
|
||||
<el-button class="step-btn" type="primary" @click="step++">保存</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-result icon="success" title="保存成功" sub-title="请退出后重新登录"></el-result>
|
||||
</div>
|
||||
<el-steps class="step-style" :active="step" align-center finish-status="success">
|
||||
<el-step title="Step 1" description="填写邮箱" />
|
||||
<el-step title="Step 2" description="填写验证码" />
|
||||
<el-step title="Step 3" description="修改密码" />
|
||||
</el-steps>
|
||||
<el-steps class="step-style" :active="step" finish-status="success" simple>
|
||||
<el-step title="填写邮箱" />
|
||||
<el-step title="填写验证码" />
|
||||
<el-step title="修改密码" />
|
||||
</el-steps>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
const step = ref(0)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.step-div {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.step-div p {
|
||||
margin-bottom: 20px;
|
||||
color: #787878;
|
||||
}
|
||||
|
||||
.step-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.step-style {
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<el-tabs v-model="message" type="card">
|
||||
<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
|
||||
<el-table :data="state.unread" :show-header="false" style="width: 100%">
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<span class="message-title">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" width="180"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleRead(scope.$index)">标为已读</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="handle-row">
|
||||
<el-button type="primary">全部标为已读</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
|
||||
<template v-if="message === 'second'">
|
||||
<el-table :data="state.read" :show-header="false" style="width: 100%">
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<span class="message-title">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" width="180"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" size="small" @click="handleDel(scope.$index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="handle-row">
|
||||
<el-button type="danger">删除全部</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
|
||||
<template v-if="message === 'third'">
|
||||
<el-table :data="state.recycle" :show-header="false" style="width: 100%">
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<span class="message-title">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" width="180"></el-table-column>
|
||||
<el-table-column width="120">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleRestore(scope.$index)">还原</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="handle-row">
|
||||
<el-button type="danger">清空回收站</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="tabs">
|
||||
import { ref, reactive } from 'vue';
|
||||
|
||||
const message = ref('first');
|
||||
const state = reactive({
|
||||
unread: [
|
||||
{
|
||||
date: '2018-04-19 20:00:00',
|
||||
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
|
||||
},
|
||||
{
|
||||
date: '2018-04-19 21:00:00',
|
||||
title: '今晚12点整发大红包,先到先得'
|
||||
}
|
||||
],
|
||||
read: [
|
||||
{
|
||||
date: '2018-04-19 20:00:00',
|
||||
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
|
||||
}
|
||||
],
|
||||
recycle: [
|
||||
{
|
||||
date: '2018-04-19 20:00:00',
|
||||
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const handleRead = (index: number) => {
|
||||
const item = state.unread.splice(index, 1);
|
||||
state.read = item.concat(state.read);
|
||||
};
|
||||
const handleDel = (index: number) => {
|
||||
const item = state.read.splice(index, 1);
|
||||
state.recycle = item.concat(state.recycle);
|
||||
};
|
||||
const handleRestore = (index: number) => {
|
||||
const item = state.recycle.splice(index, 1);
|
||||
state.read = item.concat(state.read);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.message-title {
|
||||
cursor: pointer;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.handle-row {
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<el-button type="primary" @click="open = true">开始引导</el-button>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<el-space>
|
||||
<el-button ref="ref1">上传</el-button>
|
||||
<el-button ref="ref2" type="primary">保存</el-button>
|
||||
<el-button ref="ref3" :icon="MoreFilled" />
|
||||
</el-space>
|
||||
|
||||
<el-tour v-model="open">
|
||||
<el-tour-step :target="ref1?.$el" title="上传文件">
|
||||
<img style="width: 120px" src="../../assets/img/img.jpg" alt="tour.png" />
|
||||
<div>点击这里选择文件</div>
|
||||
</el-tour-step>
|
||||
<el-tour-step :target="ref2?.$el" title="保存" description="点击进行上传" />
|
||||
<el-tour-step :target="ref3?.$el" title="更多操作" description="点击查看更多操作" />
|
||||
</el-tour>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { MoreFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const ref1 = ref()
|
||||
const ref2 = ref()
|
||||
const ref3 = ref()
|
||||
|
||||
const open = ref(false)
|
||||
</script>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,257 @@
|
|||
<template>
|
||||
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="自定义图标">
|
||||
<h2>使用方法</h2>
|
||||
<p style="line-height: 50px">
|
||||
直接通过设置类名为 el-icon-lx-iconName 来使用即可。例如:(共{{ iconList.length }}个图标)
|
||||
</p>
|
||||
<p class="example-p">
|
||||
<i class="el-icon-lx-redpacket_fill" style="font-size: 30px; color: #ff5900"></i>
|
||||
<span><i class="el-icon-lx-redpacket_fill"></i></span>
|
||||
</p>
|
||||
<p class="example-p">
|
||||
<i class="el-icon-lx-weibo" style="font-size: 30px; color: #fd5656"></i>
|
||||
<span><i class="el-icon-lx-weibo"></i></span>
|
||||
</p>
|
||||
<p class="example-p">
|
||||
<i class="el-icon-lx-emojifill" style="font-size: 30px; color: #ffc300"></i>
|
||||
<span><i class="el-icon-lx-emojifill"></i></span>
|
||||
</p>
|
||||
<br />
|
||||
<h2>图标</h2>
|
||||
<div class="search-box">
|
||||
<el-input class="search" size="large" v-model="keyword" clearable placeholder="请输入图标名称"></el-input>
|
||||
</div>
|
||||
<ul>
|
||||
<li class="icon-li" v-for="(item, index) in list" :key="index">
|
||||
<div class="icon-li-content">
|
||||
<i :class="`el-icon-lx-${item}`"></i>
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Element图标">
|
||||
<el-link type="primary" href="https://element-plus.org/zh-CN/component/icon.html#icon-collection"
|
||||
target="_blank">前往官方文档查看</el-link>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="icon">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const iconList: Array<string> = [
|
||||
'attentionforbid',
|
||||
'attentionforbidfill',
|
||||
'attention',
|
||||
'attentionfill',
|
||||
'tag',
|
||||
'tagfill',
|
||||
'people',
|
||||
'peoplefill',
|
||||
'notice',
|
||||
'noticefill',
|
||||
'mobile',
|
||||
'mobilefill',
|
||||
'voice',
|
||||
'voicefill',
|
||||
'unlock',
|
||||
'lock',
|
||||
'home',
|
||||
'homefill',
|
||||
'delete',
|
||||
'deletefill',
|
||||
'notification',
|
||||
'notificationfill',
|
||||
'notificationforbidfill',
|
||||
'like',
|
||||
'likefill',
|
||||
'comment',
|
||||
'commentfill',
|
||||
'camera',
|
||||
'camerafill',
|
||||
'warn',
|
||||
'warnfill',
|
||||
'time',
|
||||
'timefill',
|
||||
'location',
|
||||
'locationfill',
|
||||
'favor',
|
||||
'favorfill',
|
||||
'skin',
|
||||
'skinfill',
|
||||
'news',
|
||||
'newsfill',
|
||||
'record',
|
||||
'recordfill',
|
||||
'emoji',
|
||||
'emojifill',
|
||||
'message',
|
||||
'messagefill',
|
||||
'goods',
|
||||
'goodsfill',
|
||||
'crown',
|
||||
'crownfill',
|
||||
'move',
|
||||
'add',
|
||||
'hot',
|
||||
'hotfill',
|
||||
'service',
|
||||
'servicefill',
|
||||
'present',
|
||||
'presentfill',
|
||||
'pic',
|
||||
'picfill',
|
||||
'rank',
|
||||
'rankfill',
|
||||
'male',
|
||||
'female',
|
||||
'down',
|
||||
'top',
|
||||
'recharge',
|
||||
'rechargefill',
|
||||
'forward',
|
||||
'forwardfill',
|
||||
'info',
|
||||
'infofill',
|
||||
'redpacket',
|
||||
'redpacket_fill',
|
||||
'roundadd',
|
||||
'roundaddfill',
|
||||
'friendadd',
|
||||
'friendaddfill',
|
||||
'cart',
|
||||
'cartfill',
|
||||
'more',
|
||||
'moreandroid',
|
||||
'back',
|
||||
'right',
|
||||
'shop',
|
||||
'shopfill',
|
||||
'question',
|
||||
'questionfill',
|
||||
'roundclose',
|
||||
'roundclosefill',
|
||||
'roundcheck',
|
||||
'roundcheckfill',
|
||||
'global',
|
||||
'mail',
|
||||
'punch',
|
||||
'exit',
|
||||
'upload',
|
||||
'read',
|
||||
'file',
|
||||
'link',
|
||||
'full',
|
||||
'group',
|
||||
'friend',
|
||||
'profile',
|
||||
'addressbook',
|
||||
'calendar',
|
||||
'text',
|
||||
'copy',
|
||||
'share',
|
||||
'wifi',
|
||||
'vipcard',
|
||||
'weibo',
|
||||
'remind',
|
||||
'refresh',
|
||||
'filter',
|
||||
'settings',
|
||||
'scan',
|
||||
'qrcode',
|
||||
'cascades',
|
||||
'apps',
|
||||
'sort',
|
||||
'searchlist',
|
||||
'search',
|
||||
'edit',
|
||||
'apple-line',
|
||||
'baidu-fill',
|
||||
'amazon-fill',
|
||||
'netease-cloud-music-fill',
|
||||
'qq-line',
|
||||
'wechat-fill',
|
||||
'alipay-fill',
|
||||
'android-fill',
|
||||
'android-line',
|
||||
'whatsapp-line',
|
||||
'whatsapp-fill',
|
||||
'bilibili-fill',
|
||||
'chrome-fill',
|
||||
'dingding-fill',
|
||||
'dingding-line',
|
||||
'apple-fill',
|
||||
'github-fill',
|
||||
'qq-fill',
|
||||
'wechat-pay-fill',
|
||||
'windows-line',
|
||||
'windows-fill',
|
||||
'youtube-line',
|
||||
'youtube-fill',
|
||||
'wechat-pay-line',
|
||||
'zhihu-line'
|
||||
];
|
||||
|
||||
const keyword = ref('');
|
||||
const list = computed(() => {
|
||||
return iconList.filter(item => {
|
||||
return item.indexOf(keyword.value) !== -1;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.example-p {
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
ul,
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.icon-li {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.icon-li-content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-li-content i {
|
||||
font-size: 36px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.icon-li-content span {
|
||||
margin-top: 10px;
|
||||
color: #787878;
|
||||
}
|
||||
|
||||
.iframe {
|
||||
width: 100%;
|
||||
height: 700px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,171 @@
|
|||
<template>
|
||||
<div class="login-bg">
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
|
||||
<div class="login-title">后台管理系统</div>
|
||||
</div>
|
||||
<el-form :model="param" :rules="rules" ref="login" size="large">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="param.username" placeholder="用户名">
|
||||
<template #prepend>
|
||||
<el-icon>
|
||||
<User />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
v-model="param.password"
|
||||
@keyup.enter="submitForm(login)"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-icon>
|
||||
<Lock />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<div class="pwd-tips">
|
||||
<el-checkbox class="pwd-checkbox" v-model="checked" label="记住密码" />
|
||||
<el-link type="primary" @click="$router.push('/reset-pwd')">忘记密码</el-link>
|
||||
</div>
|
||||
<el-button class="login-btn" type="primary" size="large" @click="submitForm(login)">登录</el-button>
|
||||
<p class="login-tips">Tips : 用户名和密码随便填。</p>
|
||||
<p class="login-text">
|
||||
没有账号?<el-link type="primary" @click="$router.push('/register')">立即注册</el-link>
|
||||
</p>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue';
|
||||
import { useTabsStore } from '@/store/tabs';
|
||||
import { usePermissStore } from '@/store/permiss';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
|
||||
interface LoginInfo {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const lgStr = localStorage.getItem('login-param');
|
||||
const defParam = lgStr ? JSON.parse(lgStr) : null;
|
||||
const checked = ref(lgStr ? true : false);
|
||||
|
||||
const router = useRouter();
|
||||
const param = reactive<LoginInfo>({
|
||||
username: defParam ? defParam.username : '',
|
||||
password: defParam ? defParam.password : '',
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
};
|
||||
const permiss = usePermissStore();
|
||||
const login = ref<FormInstance>();
|
||||
const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
ElMessage.success('登录成功');
|
||||
localStorage.setItem('vuems_name', param.username);
|
||||
const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
|
||||
permiss.handleSet(keys);
|
||||
router.push('/');
|
||||
if (checked.value) {
|
||||
localStorage.setItem('login-param', JSON.stringify(param));
|
||||
} else {
|
||||
localStorage.removeItem('login-param');
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('登录失败');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const tabs = useTabsStore();
|
||||
tabs.clearTabs();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-bg {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 22px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 450px;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
padding: 40px 50px 50px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pwd-tips {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
margin: -10px 0 10px;
|
||||
color: #787878;
|
||||
}
|
||||
|
||||
.pwd-checkbox {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-tips {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.login-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: #787878;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="plugins-tips">
|
||||
md-editor-v3:vue3版本的 markdown 编辑器,配置丰富,请详看文档。 访问地址:
|
||||
<a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a>
|
||||
</div>
|
||||
<md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" />
|
||||
<el-button type="primary">提交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="md">
|
||||
import { ref } from 'vue';
|
||||
import MdEditor from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
|
||||
const text = ref('Hello Editor!');
|
||||
const onUploadImg = (files: any) => {
|
||||
console.log(files);
|
||||
};
|
||||
</script>
|