改为typescript;升级elementplus

pull/351/head
lin-xin 2022-08-21 16:59:12 +08:00
parent a072752609
commit 995ab69b1e
50 changed files with 3728 additions and 2630 deletions

View File

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

View File

@ -22,16 +22,6 @@
[English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md) [English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)
## 项目截图
### 登录
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png)
### 首页
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png)
## 赞助商 ## 赞助商
### 好问 ### 好问
@ -40,7 +30,7 @@
专业问卷服务,一对一客服,按需定制 专业问卷服务,一对一客服,按需定制
## 赞赏 ## 支持作者
请作者喝杯咖啡吧!(微信号linxin_20) 请作者喝杯咖啡吧!(微信号linxin_20)
@ -48,26 +38,25 @@
## 前言 ## 前言
该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统Web Management System开发。基于 Vue3 + pinia引用 Element Plus 组件库,方便开发快速简洁好看的组件。分离颜色样式,支持手动切换主题色,而且很方便使用自定义主题色 该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia引用 Element Plus 组件库,方便开发。
## 功能 ## 功能
- [x] Element Plus - [x] Element Plus
- [x] vite
- [x] pinia
- [x] 登录/注销 - [x] 登录/注销
- [x] Dashboard - [x] Dashboard
- [x] 表格 - [x] 表格
- [x] Tab 选项卡 - [x] Tab 选项卡
- [x] 表单 - [x] 表单
- [x] 图表 :bar_chart: - [x] 图表 :bar_chart:
- [x] 富文本编辑器 - [x] 富文本/markdown编辑器
- [x] 图片拖拽/裁剪上传 - [x] 图片拖拽/裁剪上传
- [x] 权限测试 - [x] 权限管理
- [x] 404 / 403
- [x] 三级菜单 - [x] 三级菜单
- [x] 自定义图标 - [x] 自定义图标
- [x] 国际化
- [x] vite
- [x] pinia
## 安装步骤 ## 安装步骤
@ -76,7 +65,7 @@ git clone https://github.com/lin-xin/vue-manage-system.git // 把模板下
cd vue-manage-system // 进入模板目录 cd vue-manage-system // 进入模板目录
npm install // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn npm install // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn
// 开启服务器,浏览器访问 http://localhost:8080 // 运行
npm run dev npm run dev
// 执行构建命令生成的dist文件夹放在服务器下即可访问 // 执行构建命令生成的dist文件夹放在服务器下即可访问
@ -98,12 +87,10 @@ vue.js 封装 sChart.js 的图表组件。访问地址:[vue-schart](https://gi
</div> </div>
</template> </template>
<script> <script setup>
import { ref } from 'vue';
import Schart from "vue-schart"; // 导入Schart组件 import Schart from "vue-schart"; // 导入Schart组件
export default { const options = ref({
data() {
return {
options: {
type: "bar", type: "bar",
title: { title: {
text: "最近一周各品类销售图", text: "最近一周各品类销售图",
@ -123,13 +110,7 @@ vue.js 封装 sChart.js 的图表组件。访问地址:[vue-schart](https://gi
data: [144, 198, 150, 235, 120], data: [144, 198, 150, 235, 120],
}, },
], ],
}, })
};
},
components: {
Schart,
},
};
</script> </script>
<style> <style>
.wrapper { .wrapper {
@ -139,6 +120,16 @@ vue.js 封装 sChart.js 的图表组件。访问地址:[vue-schart](https://gi
</style> </style>
``` ```
## 项目截图
### 登录
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms3.png)
### 首页
![Image text](https://github.com/lin-xin/manage-system/raw/master/screenshots/wms1.png)
## License ## License
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE) [MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)

View File

@ -37,13 +37,10 @@ The scheme as a set of multi-function background frame templates, suitable for m
- [x] Tabs - [x] Tabs
- [x] From - [x] From
- [x] Chart :bar_chart: - [x] Chart :bar_chart:
- [ ] Editor - [x] Editor
- [ ] Markdown - [x] Markdown
- [x] Upload pictures by clipping or dragging - [x] Upload pictures by clipping or dragging
- [ ] Support manual switch themes :sparkles:
- [ ] List drag sort
- [x] Permission - [x] Permission
- [x] 404 / 403
- [x] Three level menu - [x] Three level menu
- [x] Custom icon - [x] Custom icon
@ -55,8 +52,7 @@ The scheme as a set of multi-function background frame templates, suitable for m
## Local development ## Local development
// Open server and access http://localhost:8080 in browser npm run dev
npm run serve
## Constructing production ## Constructing production
@ -75,13 +71,10 @@ Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/v
<schart class="wrapper" canvasId="myCanvas" :options="options"></schart> <schart class="wrapper" canvasId="myCanvas" :options="options"></schart>
</div> </div>
</template> </template>
<script setup>
<script> import { ref } from 'vue';
import Schart from "vue-schart"; // 导入Schart组件 import Schart from "vue-schart"; // 导入Schart组件
export default { const options = ref({
data() {
return {
options: {
type: "bar", type: "bar",
title: { title: {
text: "最近一周各品类销售图", text: "最近一周各品类销售图",
@ -101,13 +94,7 @@ Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/v
data: [144, 198, 150, 235, 120], data: [144, 198, 150, 235, 120],
}, },
], ],
}, })
};
},
components: {
Schart,
},
};
</script> </script>
<style> <style>
.wrapper { .wrapper {
@ -117,10 +104,6 @@ Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/v
</style> </style>
``` ```
### element-ui
A desktop component library based on vue.js2.0 . Github : [element](http://element.eleme.io/#/zh-CN/component/layout)
## Screenshot ## Screenshot
### Default theme ### Default theme

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

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

52
components.d.ts vendored Normal file
View File

@ -0,0 +1,52 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElUpload: typeof import('element-plus/es')['ElUpload']
Header: typeof import('./src/components/header.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Sidebar: typeof import('./src/components/sidebar.vue')['default']
Tags: typeof import('./src/components/tags.vue')['default']
}
}

View File

@ -15,7 +15,7 @@
Please enable it to continue.</strong> Please enable it to continue.</strong>
</noscript> </noscript>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.ts"></script>
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
</body> </body>

1237
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,33 @@
{ {
"name": "vue-manage-system", "name": "vue-manage-system",
"version": "5.2.0", "version": "5.3.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vue-tsc --noEmit && vite build",
"serve": "vite preview" "serve": "vite preview"
}, },
"dependencies": { "dependencies": {
"axios": "^0.21.1", "@element-plus/icons-vue": "^2.0.9",
"element-plus": "^1.0.2-beta.52", "axios": "^0.27.2",
"md-editor-v3": "^2.2.0", "element-plus": "^2.2.14",
"pinia": "^2.0.14", "md-editor-v3": "^2.2.1",
"vue": "^3.1.2", "pinia": "^2.0.20",
"vue": "^3.2.37",
"vue-cropperjs": "^5.0.0", "vue-cropperjs": "^5.0.0",
"vue-i18n": "^9.0.0", "vue-router": "^4.1.3",
"vue-router": "^4.0.10",
"vue-schart": "^2.0.0", "vue-schart": "^2.0.0",
"wangeditor": "^4.7.4" "wangeditor": "^4.7.15"
}, },
"devDependencies": { "devDependencies": {
"vite": "2.3.7", "@vitejs/plugin-vue": "^3.0.0",
"@vitejs/plugin-vue": "^1.2.3", "@vue/compiler-sfc": "^3.1.2",
"@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": [ "browserslist": [
"> 1%", "> 1%",

View File

@ -2,11 +2,7 @@
<router-view /> <router-view />
</template> </template>
<script>
export default {};
</script>
<style> <style>
@import "./assets/css/main.css"; @import './assets/css/main.css';
@import "./assets/css/color-dark.css"; @import './assets/css/color-dark.css';
</style> </style>

View File

@ -1,9 +1,8 @@
import request from '../utils/request'; import request from '../utils/request';
export const fetchData = query => { export const fetchData = () => {
return request({ return request({
url: './table.json', url: './table.json',
method: 'get', method: 'get'
params: query
}); });
}; };

View File

@ -175,3 +175,27 @@ a {
.v-note-wrapper .v-note-panel { .v-note-wrapper .v-note-panel {
min-height: 500px; min-height: 500px;
} }
[class*=" el-icon-"], [class^=el-icon-] {
speak: none;
font-style: normal;
font-weight: 400;
font-variant: normal;
text-transform: none;
line-height: 1;
vertical-align: baseline;
display: inline-block;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.el-sub-menu [class^=el-icon-] {
vertical-align: middle;
margin-right: 5px;
width: 24px;
text-align: center;
font-size: 18px;
}
[hidden]{
display: none !important;
}

View File

@ -2,30 +2,32 @@
<div class="header"> <div class="header">
<!-- 折叠按钮 --> <!-- 折叠按钮 -->
<div class="collapse-btn" @click="collapseChage"> <div class="collapse-btn" @click="collapseChage">
<i v-if="!sidebar.collapse" class="el-icon-s-fold"></i> <el-icon v-if="sidebar.collapse"><Expand /></el-icon>
<i v-else class="el-icon-s-unfold"></i> <el-icon v-else><Fold /></el-icon>
</div> </div>
<div class="logo">后台管理系统</div> <div class="logo">后台管理系统</div>
<div class="header-right"> <div class="header-right">
<div class="header-user-con"> <div class="header-user-con">
<!-- 消息中心 --> <!-- 消息中心 -->
<div class="btn-bell"> <div class="btn-bell" @click="router.push('/tabs')">
<el-tooltip effect="dark" :content="message?`有${message}条未读消息`:`消息中心`" placement="bottom"> <el-tooltip
<router-link to="/tabs"> effect="dark"
<i class="el-icon-bell"></i> :content="message ? `有${message}条未读消息` : `消息中心`"
</router-link> placement="bottom"
>
<i class="el-icon-lx-notice"></i>
</el-tooltip> </el-tooltip>
<span class="btn-bell-badge" v-if="message"></span> <span class="btn-bell-badge" v-if="message"></span>
</div> </div>
<!-- 用户头像 --> <!-- 用户头像 -->
<div class="user-avator"> <el-avatar class="user-avator" :size="30" :src="imgurl" />
<img src="../assets/img/img.jpg" />
</div>
<!-- 用户名下拉菜单 --> <!-- 用户名下拉菜单 -->
<el-dropdown class="user-name" trigger="click" @command="handleCommand"> <el-dropdown class="user-name" trigger="click" @command="handleCommand">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
{{ username }} {{ username }}
<i class="el-icon-caret-bottom"></i> <el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
@ -41,14 +43,14 @@
</div> </div>
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { computed, onMounted } from "vue"; import { onMounted } from 'vue';
import { useSidebarStore } from '../store/sidebar' import { useSidebarStore } from '../store/sidebar';
import { useRouter } from "vue-router"; import { useRouter } from 'vue-router';
export default { import imgurl from '../assets/img/img.jpg';
setup() {
const username = localStorage.getItem("ms_username"); const username: string | null = localStorage.getItem('ms_username');
const message = 2; const message: number = 2;
const sidebar = useSidebarStore(); const sidebar = useSidebarStore();
// //
@ -64,24 +66,14 @@ export default {
// //
const router = useRouter(); const router = useRouter();
const handleCommand = (command) => { const handleCommand = (command: string) => {
if (command == "loginout") { if (command == 'loginout') {
localStorage.removeItem("ms_username"); localStorage.removeItem('ms_username');
router.push("/login"); router.push('/login');
} else if (command == "user") { } else if (command == 'user') {
router.push("/user"); router.push('/user');
} }
}; };
return {
sidebar,
username,
message,
collapseChage,
handleCommand,
};
},
};
</script> </script>
<style scoped> <style scoped>
.header { .header {
@ -93,10 +85,13 @@ export default {
color: #fff; color: #fff;
} }
.collapse-btn { .collapse-btn {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
float: left; float: left;
padding: 0 21px; padding: 0 21px;
cursor: pointer; cursor: pointer;
line-height: 70px;
} }
.header .logo { .header .logo {
float: left; float: left;
@ -125,18 +120,20 @@ export default {
text-align: center; text-align: center;
border-radius: 15px; border-radius: 15px;
cursor: pointer; cursor: pointer;
display: flex;
align-items: center;
} }
.btn-bell-badge { .btn-bell-badge {
position: absolute; position: absolute;
right: 0; right: 4px;
top: -2px; top: 0px;
width: 8px; width: 8px;
height: 8px; height: 8px;
border-radius: 4px; border-radius: 4px;
background: #f56c6c; background: #f56c6c;
color: #fff; color: #fff;
} }
.btn-bell .el-icon-bell { .btn-bell .el-icon-lx-notice {
color: #fff; color: #fff;
} }
.user-name { .user-name {
@ -145,15 +142,11 @@ export default {
.user-avator { .user-avator {
margin-left: 20px; margin-left: 20px;
} }
.user-avator img {
display: block;
width: 40px;
height: 40px;
border-radius: 50%;
}
.el-dropdown-link { .el-dropdown-link {
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;
display: flex;
align-items: center;
} }
.el-dropdown-menu__item { .el-dropdown-menu__item {
text-align: center; text-align: center;

View File

@ -1,28 +1,47 @@
<template> <template>
<div class="sidebar"> <div class="sidebar">
<el-menu class="sidebar-el-menu" :default-active="onRoutes" :collapse="sidebar.collapse" background-color="#324157" <el-menu
text-color="#bfcbd9" active-text-color="#20a0ff" unique-opened router> class="sidebar-el-menu"
:default-active="onRoutes"
:collapse="sidebar.collapse"
background-color="#324157"
text-color="#bfcbd9"
active-text-color="#20a0ff"
unique-opened
router
>
<template v-for="item in items"> <template v-for="item in items">
<template v-if="item.subs"> <template v-if="item.subs">
<el-submenu :index="item.index" :key="item.index"> <el-sub-menu :index="item.index" :key="item.index" v-permiss="item.permiss">
<template #title> <template #title>
<i :class="item.icon"></i> <el-icon>
<component :is="item.icon"></component>
</el-icon>
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
</template> </template>
<template v-for="subItem in item.subs"> <template v-for="subItem in item.subs">
<el-submenu v-if="subItem.subs" :index="subItem.index" :key="subItem.index"> <el-sub-menu
v-if="subItem.subs"
:index="subItem.index"
:key="subItem.index"
v-permiss="item.permiss"
>
<template #title>{{ subItem.title }}</template> <template #title>{{ subItem.title }}</template>
<el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index"> <el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">
{{ threeItem.title }}</el-menu-item> {{ threeItem.title }}
</el-submenu> </el-menu-item>
<el-menu-item v-else :index="subItem.index" :key="subItem.index">{{ subItem.title }} </el-sub-menu>
<el-menu-item v-else :index="subItem.index" v-permiss="item.permiss">
{{ subItem.title }}
</el-menu-item> </el-menu-item>
</template> </template>
</el-submenu> </el-sub-menu>
</template> </template>
<template v-else> <template v-else>
<el-menu-item :index="item.index" :key="item.index"> <el-menu-item :index="item.index" :key="item.index" v-permiss="item.permiss">
<i :class="item.icon"></i> <el-icon>
<component :is="item.icon"></component>
</el-icon>
<template #title>{{ item.title }}</template> <template #title>{{ item.title }}</template>
</el-menu-item> </el-menu-item>
</template> </template>
@ -31,92 +50,89 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { computed } from "vue"; import { computed } from 'vue';
import { useSidebarStore } from '../store/sidebar' import { useSidebarStore } from '../store/sidebar';
import { useRoute } from "vue-router"; import { useRoute } from 'vue-router';
export default {
setup() {
const items = [ const items = [
{ {
icon: "el-icon-lx-home", icon: 'Odometer',
index: "/dashboard", index: '/dashboard',
title: "系统首页", title: '系统首页',
permiss: '1'
}, },
{ {
icon: "el-icon-lx-cascades", icon: 'Calendar',
index: "/table", index: '/table',
title: "基础表格", title: '基础表格',
permiss: '2'
}, },
{ {
icon: "el-icon-lx-copy", icon: 'DocumentCopy',
index: "/tabs", index: '/tabs',
title: "tab选项卡", title: 'tab选项卡',
permiss: '3'
}, },
{ {
icon: "el-icon-lx-calendar", icon: 'Edit',
index: "3", index: '3',
title: "表单相关", title: '表单相关',
permiss: '4',
subs: [ subs: [
{ {
index: "/form", index: '/form',
title: "基本表单", title: '基本表单',
permiss: '5'
}, },
{ {
index: "/upload", index: '/upload',
title: "文件上传", title: '文件上传',
permiss: '6'
}, },
{ {
index: "4", index: '4',
title: "三级菜单", title: '三级菜单',
permiss: '7',
subs: [ subs: [
{ {
index: "/editor", index: '/editor',
title: "富文本编辑器", title: '富文本编辑器',
permiss: '8'
}, },
{ {
index: "/markdown", index: '/markdown',
title: "markdown编辑器", title: 'markdown编辑器',
}, permiss: '9'
], }
}, ]
], }
]
}, },
{ {
icon: "el-icon-lx-emoji", icon: 'Setting',
index: "/icon", index: '/icon',
title: "自定义图标", title: '自定义图标',
permiss: '10'
}, },
{ {
icon: "el-icon-pie-chart", icon: 'PieChart',
index: "/charts", index: '/charts',
title: "schart图表", title: 'schart图表',
permiss: '11'
}, },
{ {
icon: "el-icon-lx-global", icon: 'Warning',
index: "/i18n", index: '/permission',
title: "国际化功能", title: '权限管理',
permiss: '13'
}, },
{ {
icon: "el-icon-lx-warn", icon: 'CoffeeCup',
index: "7", index: '/donate',
title: "错误处理", title: '支持作者',
subs: [ permiss: '14'
{ }
index: "/permission",
title: "权限测试",
},
{
index: "/404",
title: "404页面",
},
],
},
{
icon: "el-icon-lx-redpacket_fill",
index: "/donate",
title: "支持作者",
},
]; ];
const route = useRoute(); const route = useRoute();
@ -125,14 +141,6 @@ export default {
}); });
const sidebar = useSidebarStore(); const sidebar = useSidebarStore();
return {
items,
onRoutes,
sidebar,
};
},
};
</script> </script>
<style scoped> <style scoped>

View File

@ -1,18 +1,23 @@
<template> <template>
<div class="tags" v-if="tags.show"> <div class="tags" v-if="tags.show">
<ul> <ul>
<li class="tags-li" v-for="(item,index) in tags.list" :class="{'active': isActive(item.path)}" :key="index"> <li
class="tags-li"
v-for="(item, index) in tags.list"
:class="{ active: isActive(item.path) }"
:key="index"
>
<router-link :to="item.path" class="tags-li-title">{{ item.title }}</router-link> <router-link :to="item.path" class="tags-li-title">{{ item.title }}</router-link>
<span class="tags-li-icon" @click="closeTags(index)"> <el-icon @click="closeTags(index)"><Close /></el-icon>
<i class="el-icon-close"></i>
</span>
</li> </li>
</ul> </ul>
<div class="tags-close-box"> <div class="tags-close-box">
<el-dropdown @command="handleTags"> <el-dropdown @command="handleTags">
<el-button size="mini" type="primary"> <el-button size="small" type="primary">
标签选项 标签选项
<i class="el-icon-arrow-down el-icon--right"></i> <el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu size="small"> <el-dropdown-menu size="small">
@ -25,33 +30,32 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { useTagsStore } from '../store/tags' import { useTagsStore } from '../store/tags';
import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router"; import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
export default {
setup() {
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const isActive = (path) => { const isActive = (path: string) => {
return path === route.fullPath; return path === route.fullPath;
}; };
const tags = useTagsStore(); const tags = useTagsStore();
// //
const closeTags = (index) => { const closeTags = (index: number) => {
const delItem = tags.list[index]; const delItem = tags.list[index];
tags.delTagsItem(index); tags.delTagsItem(index);
const item = tags.list[index] ? tags.list[index] : tags.list[index - 1]; const item = tags.list[index] ? tags.list[index] : tags.list[index - 1];
if (item) { if (item) {
delItem.path === route.fullPath && router.push(item.path); delItem.path === route.fullPath && router.push(item.path);
} else { } else {
router.push("/"); router.push('/');
} }
}; };
// //
const setTags = (route) => { const setTags = (route: any) => {
const isExist = tags.list.some((item) => { const isExist = tags.list.some(item => {
return item.path === route.fullPath; return item.path === route.fullPath;
}); });
if (!isExist) { if (!isExist) {
@ -59,29 +63,29 @@ export default {
tags.setTagsItem({ tags.setTagsItem({
name: route.name, name: route.name,
title: route.meta.title, title: route.meta.title,
path: route.fullPath, path: route.fullPath
}); });
} }
}; };
setTags(route); setTags(route);
onBeforeRouteUpdate((to) => { onBeforeRouteUpdate(to => {
setTags(to); setTags(to);
}); });
// //
const closeAll = () => { const closeAll = () => {
tags.clearTags(); tags.clearTags();
router.push("/"); router.push('/');
}; };
// //
const closeOther = () => { const closeOther = () => {
const curItem = tags.list.filter((item) => { const curItem = tags.list.filter(item => {
return item.path === route.fullPath; return item.path === route.fullPath;
}); });
tags.closeTagsOther(curItem); tags.closeTagsOther(curItem);
}; };
const handleTags = (command) => { const handleTags = (command: string) => {
command === "other" ? closeOther() : closeAll(); command === 'other' ? closeOther() : closeAll();
}; };
// //
@ -89,18 +93,8 @@ export default {
// $router: router, // $router: router,
// $route: route // $route: route
// }); // });
return {
isActive,
tags,
closeTags,
handleTags,
};
},
};
</script> </script>
<style> <style>
.tags { .tags {
position: relative; position: relative;
@ -118,6 +112,8 @@ export default {
} }
.tags-li { .tags-li {
display: flex;
align-items: center;
float: left; float: left;
margin: 3px 5px 2px 3px; margin: 3px 5px 2px 3px;
border-radius: 3px; border-radius: 3px;
@ -125,11 +121,9 @@ export default {
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
height: 23px; height: 23px;
line-height: 23px;
border: 1px solid #e9eaec; border: 1px solid #e9eaec;
background: #fff; background: #fff;
padding: 0 5px 0 12px; padding: 0 5px 0 12px;
vertical-align: middle;
color: #666; color: #666;
-webkit-transition: all 0.3s ease-in; -webkit-transition: all 0.3s ease-in;
-moz-transition: all 0.3s ease-in; -moz-transition: all 0.3s ease-in;

View File

@ -1,11 +0,0 @@
import {createApp} from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import installElementPlus from './plugins/element'
import './assets/css/icon.css'
const app = createApp(App)
installElementPlus(app)
app.use(createPinia())
.use(router)
.mount('#app')

31
src/main.ts Normal file
View File

@ -0,0 +1,31 @@
import {createApp} from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
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)
app.use(ElementPlus)
// 注册elementplus图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 自定义权限指令
const permiss = usePermissStore()
app.directive('permiss', {
mounted(el, binding) {
if(!permiss.key.includes(String(binding.value))){
el['hidden'] = true;
}
}
})
app.mount('#app')

View File

@ -1,17 +0,0 @@
import ElementPlus from 'element-plus'
import { createI18n } from 'vue-i18n'
import 'element-plus/lib/theme-chalk/index.css'
import localeZH from 'element-plus/lib/locale/lang/zh-cn'
import localeEN from 'element-plus/lib/locale/lang/en'
import messages from '../utils/i18n'
const i18n = createI18n({
locale: localeZH.name,
fallbackLocale: localeEN.name,
messages,
})
export default (app) => {
app.use(ElementPlus, { locale:localeZH })
app.use(i18n)
}

View File

@ -1,7 +1,8 @@
import {createRouter, createWebHashHistory} from "vue-router"; import {createRouter, createWebHashHistory, RouteRecordRaw} from "vue-router";
import { usePermissStore } from '../store/permiss'
import Home from "../views/Home.vue"; import Home from "../views/Home.vue";
const routes = [ const routes:RouteRecordRaw[] = [
{ {
path: '/', path: '/',
redirect: '/dashboard' redirect: '/dashboard'
@ -14,108 +15,97 @@ const routes = [
path: "/dashboard", path: "/dashboard",
name: "dashboard", name: "dashboard",
meta: { meta: {
title: '系统首页' title: '系统首页',
permiss: '1'
}, },
component: () => import ( /* webpackChunkName: "dashboard" */ "../views/Dashboard.vue") component: () => import ( /* webpackChunkName: "dashboard" */ "../views/dashboard.vue")
}, { }, {
path: "/table", path: "/table",
name: "basetable", name: "basetable",
meta: { meta: {
title: '表格' title: '表格',
permiss: '2'
}, },
component: () => import ( /* webpackChunkName: "table" */ "../views/BaseTable.vue") component: () => import ( /* webpackChunkName: "table" */ "../views/table.vue")
}, { }, {
path: "/charts", path: "/charts",
name: "basecharts", name: "basecharts",
meta: { meta: {
title: '图表' title: '图表',
permiss: '11'
}, },
component: () => import ( /* webpackChunkName: "charts" */ "../views/BaseCharts.vue") component: () => import ( /* webpackChunkName: "charts" */ "../views/charts.vue")
}, { }, {
path: "/form", path: "/form",
name: "baseform", name: "baseform",
meta: { meta: {
title: '表单' title: '表单',
permiss: '5'
}, },
component: () => import ( /* webpackChunkName: "form" */ "../views/BaseForm.vue") component: () => import ( /* webpackChunkName: "form" */ "../views/form.vue")
}, { }, {
path: "/tabs", path: "/tabs",
name: "tabs", name: "tabs",
meta: { meta: {
title: 'tab标签' title: 'tab标签',
permiss: '3'
}, },
component: () => import ( /* webpackChunkName: "tabs" */ "../views/Tabs.vue") component: () => import ( /* webpackChunkName: "tabs" */ "../views/tabs.vue")
}, { }, {
path: "/donate", path: "/donate",
name: "donate", name: "donate",
meta: { meta: {
title: '鼓励作者' title: '鼓励作者',
permiss: '14'
}, },
component: () => import ( /* webpackChunkName: "donate" */ "../views/Donate.vue") component: () => import ( /* webpackChunkName: "donate" */ "../views/donate.vue")
}, { }, {
path: "/permission", path: "/permission",
name: "permission", name: "permission",
meta: { meta: {
title: '权限管理', title: '权限管理',
permission: true permiss: '13'
}, },
component: () => import ( /* webpackChunkName: "permission" */ "../views/Permission.vue") component: () => import ( /* webpackChunkName: "permission" */ "../views/permission.vue")
}, {
path: "/i18n",
name: "i18n",
meta: {
title: '国际化语言'
},
component: () => import ( /* webpackChunkName: "i18n" */ "../views/I18n.vue")
}, { }, {
path: "/upload", path: "/upload",
name: "upload", name: "upload",
meta: { meta: {
title: '上传插件' title: '上传插件',
permiss: '6'
}, },
component: () => import ( /* webpackChunkName: "upload" */ "../views/Upload.vue") component: () => import ( /* webpackChunkName: "upload" */ "../views/upload.vue")
}, { }, {
path: "/icon", path: "/icon",
name: "icon", name: "icon",
meta: { meta: {
title: '自定义图标' title: '自定义图标',
permiss: '10'
}, },
component: () => import ( /* webpackChunkName: "icon" */ "../views/Icon.vue") component: () => import ( /* webpackChunkName: "icon" */ "../views/icon.vue")
}, {
path: '/404',
name: '404',
meta: {
title: '找不到页面'
},
component: () => import (/* webpackChunkName: "404" */ '../views/404.vue')
}, {
path: '/403',
name: '403',
meta: {
title: '没有权限'
},
component: () => import (/* webpackChunkName: "403" */ '../views/403.vue')
}, { }, {
path: '/user', path: '/user',
name: 'user', name: 'user',
meta: { meta: {
title: '个人中心' title: '个人中心'
}, },
component: () => import (/* webpackChunkName: "user" */ '../views/User.vue') component: () => import (/* webpackChunkName: "user" */ '../views/user.vue')
}, { }, {
path: '/editor', path: '/editor',
name: 'editor', name: 'editor',
meta: { meta: {
title: '富文本编辑器' title: '富文本编辑器',
permiss: '8'
}, },
component: () => import (/* webpackChunkName: "editor" */ '../views/Editor.vue') component: () => import (/* webpackChunkName: "editor" */ '../views/editor.vue')
}, { }, {
path: '/markdown', path: '/markdown',
name: 'markdown', name: 'markdown',
meta: { meta: {
title: 'markdown编辑器' title: 'markdown编辑器',
permiss: '9'
}, },
component: () => import (/* webpackChunkName: "markdown" */ '../views/Markdown.vue') component: () => import (/* webpackChunkName: "markdown" */ '../views/markdown.vue')
} }
] ]
}, { }, {
@ -124,8 +114,15 @@ const routes = [
meta: { meta: {
title: '登录' title: '登录'
}, },
component: () => import ( /* webpackChunkName: "login" */ "../views/Login.vue") component: () => import ( /* webpackChunkName: "login" */ "../views/login.vue")
} }, {
path: '/403',
name: '403',
meta: {
title: '没有权限'
},
component: () => import (/* webpackChunkName: "403" */ '../views/403.vue')
},
]; ];
const router = createRouter({ const router = createRouter({
@ -136,13 +133,12 @@ const router = createRouter({
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
document.title = `${to.meta.title} | vue-manage-system`; document.title = `${to.meta.title} | vue-manage-system`;
const role = localStorage.getItem('ms_username'); const role = localStorage.getItem('ms_username');
const permiss = usePermissStore();
if (!role && to.path !== '/login') { if (!role && to.path !== '/login') {
next('/login'); next('/login');
} else if (to.meta.permission) { } else if (to.meta.permiss && !permiss.key.includes(to.meta.permiss)) {
// 如果是管理员权限则可进入,这里只是简单的模拟管理员权限而已 // 如果没有权限则进入403
role === 'admin' next('/403');
? next()
: next('/403');
} else { } else {
next(); next();
} }

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

@ -0,0 +1,23 @@
import { defineStore } from 'pinia';
interface ObjectList {
[key: string]: string[];
}
export const usePermissStore = defineStore('permiss', {
state: () => {
const keys = localStorage.getItem('ms_keys');
return {
key: keys ? JSON.parse(keys) : <string[]>[],
defaultList: <ObjectList>{
admin: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16'],
user: ['1', '2', '3', '11', '13', '14', '15']
}
};
},
actions: {
handleSet(val: string[]) {
this.key = val;
}
}
});

View File

@ -1,17 +0,0 @@
import { defineStore } from 'pinia'
export const useSidebarStore = defineStore('sidebar', {
state: () => {
return {
collapse: false
}
},
getters: {
},
actions: {
handleCollapse() {
this.collapse = !this.collapse;
}
}
})

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

@ -0,0 +1,15 @@
import { defineStore } from 'pinia';
export const useSidebarStore = defineStore('sidebar', {
state: () => {
return {
collapse: false
};
},
getters: {},
actions: {
handleCollapse() {
this.collapse = !this.collapse;
}
}
});

View File

@ -1,48 +0,0 @@
import { defineStore } from 'pinia'
export const useTagsStore = defineStore('tags', {
state: () => {
return {
list: []
}
},
getters: {
show: (state) => {
return state.list.length > 0;
},
nameList: (state) => {
return state.list.map(item => item.name);
}
},
actions: {
delTagsItem(index) {
this.list.splice(index, 1);
},
setTagsItem(data) {
this.list.push(data)
},
clearTags() {
this.list = []
},
closeTagsOther(data) {
this.list = data;
},
closeCurrentTag(data) {
console.log(data)
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;
}
}
},
}
})

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

@ -0,0 +1,53 @@
import { defineStore } from 'pinia';
interface ListItem {
name: string;
path: string;
title: string;
}
export const useTagsStore = defineStore('tags', {
state: () => {
return {
list: <ListItem[]>[]
};
},
getters: {
show: state => {
return state.list.length > 0;
},
nameList: state => {
return state.list.map(item => item.name);
}
},
actions: {
delTagsItem(index: number) {
this.list.splice(index, 1);
},
setTagsItem(data: ListItem) {
this.list.push(data);
},
clearTags() {
this.list = [];
},
closeTagsOther(data: ListItem[]) {
this.list = data;
},
closeCurrentTag(data: any) {
for (let i = 0, len = this.list.length; i < len; i++) {
const item = this.list[i];
if (item.path === data.$route.fullPath) {
if (i < len - 1) {
data.$router.push(this.list[i + 1].path);
} else if (i > 0) {
data.$router.push(this.list[i - 1].path);
} else {
data.$router.push('/');
}
this.list.splice(i, 1);
break;
}
}
}
}
});

View File

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

View File

@ -1,31 +1,28 @@
import axios from 'axios'; import axios, {AxiosInstance, AxiosError, AxiosResponse, AxiosRequestConfig} from 'axios';
const service = axios.create({ const service:AxiosInstance = axios.create({
// process.env.NODE_ENV === 'development' 来判断是否开发环境
// easy-mock服务挂了暂时不使用了
// baseURL: 'https://www.easy-mock.com/mock/592501a391470c0ac1fab128',
timeout: 5000 timeout: 5000
}); });
service.interceptors.request.use( service.interceptors.request.use(
config => { (config: AxiosRequestConfig) => {
return config; return config;
}, },
error => { (error: AxiosError) => {
console.log(error); console.log(error);
return Promise.reject(); return Promise.reject();
} }
); );
service.interceptors.response.use( service.interceptors.response.use(
response => { (response: AxiosResponse) => {
if (response.status === 200) { if (response.status === 200) {
return response.data; return response;
} else { } else {
Promise.reject(); Promise.reject();
} }
}, },
error => { (error: AxiosError) => {
console.log(error); console.log(error);
return Promise.reject(); return Promise.reject();
} }

View File

@ -11,23 +11,15 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts" name="403">
import { useRouter } from "vue-router"; import { useRouter } from 'vue-router';
export default {
name: "404",
setup() {
const router = useRouter(); const router = useRouter();
const goBack = () => { const goBack = () => {
router.go(-1); router.go(-2);
};
return {
goBack,
};
},
}; };
</script> </script>
<style scoped> <style scoped>
.error-page { .error-page {
display: flex; display: flex;

View File

@ -11,23 +11,15 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts" name="404">
import { useRouter } from "vue-router"; import { useRouter } from 'vue-router';
export default {
name: "404",
setup() {
const router = useRouter(); const router = useRouter();
const goBack = () => { const goBack = () => {
router.go(-1); router.go(-1);
}; };
return {
goBack,
};
},
};
</script> </script>
<style scoped> <style scoped>
.error-page { .error-page {
display: flex; display: flex;

View File

@ -1,158 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-pie-chart"></i> schart图表
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="plugins-tips">
vue-schartvue.js封装sChart.js的图表组件
访问地址
<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
</div>
<div class="schart-box">
<div class="content-title">柱状图</div>
<schart class="schart" canvasId="bar" :options="options1"></schart>
</div>
<div class="schart-box">
<div class="content-title">折线图</div>
<schart class="schart" canvasId="line" :options="options2"></schart>
</div>
<div class="schart-box">
<div class="content-title">饼状图</div>
<schart class="schart" canvasId="pie" :options="options3"></schart>
</div>
<div class="schart-box">
<div class="content-title">环形图</div>
<schart class="schart" canvasId="ring" :options="options4"></schart>
</div>
</div>
</div>
</template>
<script>
import Schart from "vue-schart";
export default {
name: "basecharts",
components: {
Schart,
},
setup() {
const options1 = {
type: "bar",
title: {
text: "最近一周各品类销售图",
},
bgColor: "#fbfbfb",
labels: ["周一", "周二", "周三", "周四", "周五"],
datasets: [
{
label: "家电",
fillColor: "rgba(241, 49, 74, 0.5)",
data: [234, 278, 270, 190, 230],
},
{
label: "百货",
data: [164, 178, 190, 135, 160],
},
{
label: "食品",
data: [144, 198, 150, 235, 120],
},
],
};
const options2 = {
type: "line",
title: {
text: "最近几个月各品类销售趋势图",
},
bgColor: "#fbfbfb",
labels: ["6月", "7月", "8月", "9月", "10月"],
datasets: [
{
label: "家电",
data: [234, 278, 270, 190, 230],
},
{
label: "百货",
data: [164, 178, 150, 135, 160],
},
{
label: "食品",
data: [114, 138, 200, 235, 190],
},
],
};
const options3 = {
type: "pie",
title: {
text: "服装品类销售饼状图",
},
legend: {
position: "left",
},
bgColor: "#fbfbfb",
labels: [
"T恤",
"牛仔裤",
"连衣裙",
"毛衣",
"七分裤",
"短裙",
"羽绒服",
],
datasets: [
{
data: [334, 278, 190, 235, 260, 200, 141],
},
],
};
const options4 = {
type: "ring",
title: {
text: "环形三等分",
},
showValue: false,
legend: {
position: "bottom",
bottom: 40,
},
bgColor: "#fbfbfb",
labels: ["vue", "react", "angular"],
datasets: [
{
data: [500, 500, 500],
},
],
};
return {
options1,
options2,
options3,
options4,
};
},
};
</script>
<style scoped>
.schart-box {
display: inline-block;
margin: 20px;
}
.schart {
width: 600px;
height: 400px;
}
.content-title {
clear: both;
font-weight: 400;
line-height: 50px;
margin: 10px 0;
font-size: 22px;
color: #1f2f3d;
}
</style>

View File

@ -1,174 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-calendar"></i> 表单
</el-breadcrumb-item>
<el-breadcrumb-item>基本表单</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="form-box">
<el-form ref="formRef" :rules="rules" :model="form" label-width="80px">
<el-form-item label="表单名称" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="选择器" prop="region">
<el-select v-model="form.region" placeholder="请选择">
<el-option key="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-form-item prop="date1">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date1"
style="width: 100%;"></el-date-picker>
</el-form-item>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%;">
</el-time-picker>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="城市级联" prop="options">
<el-cascader :options="options" v-model="form.options"></el-cascader>
</el-form-item>
<el-form-item label="选择开关" prop="delivery">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<el-form-item label="多选框" prop="type">
<el-checkbox-group v-model="form.type">
<el-checkbox label="步步高" name="type"></el-checkbox>
<el-checkbox label="小天才" name="type"></el-checkbox>
<el-checkbox label="imoo" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="单选框" prop="resource">
<el-radio-group v-model="form.resource">
<el-radio label="步步高"></el-radio>
<el-radio label="小天才"></el-radio>
<el-radio label="imoo"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="文本框" prop="desc">
<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit"></el-button>
<el-button @click="onReset"></el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script>
import { reactive, ref } from "vue";
import { ElMessage } from "element-plus";
export default {
name: "baseform",
setup() {
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 = {
name: [
{ required: true, message: "请输入表单名称", trigger: "blur" },
],
};
const formRef = ref(null);
const form = reactive({
name: "",
region: "",
date1: "",
date2: "",
delivery: true,
type: ["步步高"],
resource: "小天才",
desc: "",
options: [],
});
//
const onSubmit = () => {
//
formRef.value.validate((valid) => {
if (valid) {
console.log(form);
ElMessage.success("提交成功!");
} else {
return false;
}
});
};
//
const onReset = () => {
formRef.value.resetFields();
};
return {
options,
rules,
formRef,
form,
onSubmit,
onReset,
};
},
};
</script>

View File

@ -1,196 +0,0 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-cascades"></i> 基础表格
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<div class="handle-box">
<el-select v-model="query.address" placeholder="地址" class="handle-select mr10">
<el-option key="1" label="广东省" value="广东省"></el-option>
<el-option key="2" label="湖南省" value="湖南省"></el-option>
</el-select>
<el-input v-model="query.name" placeholder="用户名" class="handle-input mr10"></el-input>
<el-button type="primary" icon="el-icon-search" @click="handleSearch"></el-button>
</div>
<el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
<el-table-column prop="name" label="用户名"></el-table-column>
<el-table-column label="账户余额">
<template #default="scope">{{ scope.row.money }}</template>
</el-table-column>
<el-table-column label="头像(查看大图)" align="center">
<template #default="scope">
<el-image class="table-td-thumb" :src="scope.row.thumb" :preview-src-list="[scope.row.thumb]">
</el-image>
</template>
</el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
<el-table-column label="状态" align="center">
<template #default="scope">
<el-tag :type="
scope.row.state === '成功'
? 'success'
: scope.row.state === '失败'
? 'danger'
: ''
">{{ scope.row.state }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="date" label="注册时间"></el-table-column>
<el-table-column label="操作" width="180" align="center">
<template #default="scope">
<el-button type="text" icon="el-icon-edit" @click="handleEdit(scope.$index, scope.row)">编辑
</el-button>
<el-button type="text" icon="el-icon-delete" class="red"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination background layout="total, prev, pager, next" :current-page="query.pageIndex"
:page-size="query.pageSize" :total="pageTotal" @current-change="handlePageChange"></el-pagination>
</div>
</div>
<!-- 编辑弹出框 -->
<el-dialog title="编辑" v-model="editVisible" width="30%">
<el-form label-width="70px">
<el-form-item label="用户名">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="地址">
<el-input v-model="form.address"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="editVisible = false"> </el-button>
<el-button type="primary" @click="saveEdit"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import { ref, reactive } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { fetchData } from "../api/index";
export default {
name: "basetable",
setup() {
const query = reactive({
address: "",
name: "",
pageIndex: 1,
pageSize: 10,
});
const tableData = ref([]);
const pageTotal = ref(0);
//
const getData = () => {
fetchData(query).then((res) => {
tableData.value = res.list;
pageTotal.value = res.pageTotal || 50;
});
};
getData();
//
const handleSearch = () => {
query.pageIndex = 1;
getData();
};
//
const handlePageChange = (val) => {
query.pageIndex = val;
getData();
};
//
const handleDelete = (index) => {
//
ElMessageBox.confirm("确定要删除吗?", "提示", {
type: "warning",
})
.then(() => {
ElMessage.success("删除成功");
tableData.value.splice(index, 1);
})
.catch(() => {});
};
//
const editVisible = ref(false);
let form = reactive({
name: "",
address: "",
});
let idx = -1;
const handleEdit = (index, row) => {
idx = index;
Object.keys(form).forEach((item) => {
form[item] = row[item];
});
editVisible.value = true;
};
const saveEdit = () => {
editVisible.value = false;
ElMessage.success(`修改第 ${idx + 1} 行成功`);
Object.keys(form).forEach((item) => {
tableData.value[idx][item] = form[item];
});
};
return {
query,
tableData,
pageTotal,
editVisible,
form,
handleSearch,
handlePageChange,
handleDelete,
handleEdit,
saveEdit,
};
},
};
</script>
<style scoped>
.handle-box {
margin-bottom: 20px;
}
.handle-select {
width: 120px;
}
.handle-input {
width: 300px;
display: inline-block;
}
.table {
width: 100%;
font-size: 14px;
}
.red {
color: #ff0000;
}
.mr10 {
margin-right: 10px;
}
.table-td-thumb {
display: block;
margin: auto;
width: 40px;
height: 40px;
}
</style>

View File

@ -2,9 +2,9 @@
<div> <div>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="8"> <el-col :span="8">
<el-card shadow="hover" class="mgb20" style="height:252px;"> <el-card shadow="hover" class="mgb20" style="height: 252px">
<div class="user-info"> <div class="user-info">
<img src="../assets/img/img.jpg" class="user-avator" alt /> <el-avatar :size="120" :src="imgurl" />
<div class="user-info-cont"> <div class="user-info-cont">
<div class="user-info-name">{{ name }}</div> <div class="user-info-name">{{ name }}</div>
<div>{{ role }}</div> <div>{{ role }}</div>
@ -12,14 +12,14 @@
</div> </div>
<div class="user-info-list"> <div class="user-info-list">
上次登录时间 上次登录时间
<span>2019-11-01</span> <span>2022-10-01</span>
</div> </div>
<div class="user-info-list"> <div class="user-info-list">
上次登录地点 上次登录地点
<span>东莞</span> <span>东莞</span>
</div> </div>
</el-card> </el-card>
<el-card shadow="hover" style="height:252px;"> <el-card shadow="hover" style="height: 252px">
<template #header> <template #header>
<div class="clearfix"> <div class="clearfix">
<span>语言详情</span> <span>语言详情</span>
@ -37,7 +37,7 @@
<el-col :span="8"> <el-col :span="8">
<el-card shadow="hover" :body-style="{ padding: '0px' }"> <el-card shadow="hover" :body-style="{ padding: '0px' }">
<div class="grid-content grid-con-1"> <div class="grid-content grid-con-1">
<i class="el-icon-user-solid grid-con-icon"></i> <el-icon class="grid-con-icon"><User /></el-icon>
<div class="grid-cont-right"> <div class="grid-cont-right">
<div class="grid-num">1234</div> <div class="grid-num">1234</div>
<div>用户访问量</div> <div>用户访问量</div>
@ -48,7 +48,7 @@
<el-col :span="8"> <el-col :span="8">
<el-card shadow="hover" :body-style="{ padding: '0px' }"> <el-card shadow="hover" :body-style="{ padding: '0px' }">
<div class="grid-content grid-con-2"> <div class="grid-content grid-con-2">
<i class="el-icon-message-solid grid-con-icon"></i> <el-icon class="grid-con-icon"><ChatDotRound /></el-icon>
<div class="grid-cont-right"> <div class="grid-cont-right">
<div class="grid-num">321</div> <div class="grid-num">321</div>
<div>系统消息</div> <div>系统消息</div>
@ -59,24 +59,24 @@
<el-col :span="8"> <el-col :span="8">
<el-card shadow="hover" :body-style="{ padding: '0px' }"> <el-card shadow="hover" :body-style="{ padding: '0px' }">
<div class="grid-content grid-con-3"> <div class="grid-content grid-con-3">
<i class="el-icon-s-goods grid-con-icon"></i> <el-icon class="grid-con-icon"><Goods /></el-icon>
<div class="grid-cont-right"> <div class="grid-cont-right">
<div class="grid-num">5000</div> <div class="grid-num">5000</div>
<div>数量</div> <div>商品数量</div>
</div> </div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
<el-card shadow="hover" style="height:403px;"> <el-card shadow="hover" style="height: 403px">
<template #header> <template #header>
<div class="clearfix"> <div class="clearfix">
<span>待办事项</span> <span>待办事项</span>
<el-button style="float: right; padding: 3px 0" type="text">添加</el-button> <el-button style="float: right; padding: 3px 0" text>添加</el-button>
</div> </div>
</template> </template>
<el-table :show-header="false" :data="todoList" style="width:100%;"> <el-table :show-header="false" :data="todoList" style="width: 100%">
<el-table-column width="40"> <el-table-column width="40">
<template #default="scope"> <template #default="scope">
<el-checkbox v-model="scope.row.status"></el-checkbox> <el-checkbox v-model="scope.row.status"></el-checkbox>
@ -84,15 +84,14 @@
</el-table-column> </el-table-column>
<el-table-column> <el-table-column>
<template #default="scope"> <template #default="scope">
<div class="todo-item" :class="{ <div
'todo-item-del': scope.row.status, class="todo-item"
}">{{ scope.row.title }}</div> :class="{
</template> 'todo-item-del': scope.row.status
</el-table-column> }"
<el-table-column width="60"> >
<template> {{ scope.row.title }}
<i class="el-icon-edit"></i> </div>
<i class="el-icon-delete"></i>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -114,126 +113,83 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts" name="dashboard">
import Schart from "vue-schart"; import Schart from 'vue-schart';
import { reactive } from "vue"; import { reactive } from 'vue';
export default { import imgurl from '../assets/img/img.jpg';
name: "dashboard",
components: { Schart }, const name = localStorage.getItem('ms_username');
setup() { const role: string = name === 'admin' ? '超级管理员' : '普通用户';
const name = localStorage.getItem("ms_username");
const role = name === "admin" ? "超级管理员" : "普通用户";
const data = reactive([
{
name: "2018/09/04",
value: 1083,
},
{
name: "2018/09/05",
value: 941,
},
{
name: "2018/09/06",
value: 1139,
},
{
name: "2018/09/07",
value: 816,
},
{
name: "2018/09/08",
value: 327,
},
{
name: "2018/09/09",
value: 228,
},
{
name: "2018/09/10",
value: 1065,
},
]);
const options = { const options = {
type: "bar", type: 'bar',
title: { title: {
text: "最近一周各品类销售图", text: '最近一周各品类销售图'
}, },
xRorate: 25, xRorate: 25,
labels: ["周一", "周二", "周三", "周四", "周五"], labels: ['周一', '周二', '周三', '周四', '周五'],
datasets: [ datasets: [
{ {
label: "家电", label: '家电',
data: [234, 278, 270, 190, 230], data: [234, 278, 270, 190, 230]
}, },
{ {
label: "百货", label: '百货',
data: [164, 178, 190, 135, 160], data: [164, 178, 190, 135, 160]
}, },
{ {
label: "食品", label: '食品',
data: [144, 198, 150, 235, 120], data: [144, 198, 150, 235, 120]
}, }
], ]
}; };
const options2 = { const options2 = {
type: "line", type: 'line',
title: { title: {
text: "最近几个月各品类销售趋势图", text: '最近几个月各品类销售趋势图'
}, },
labels: ["6月", "7月", "8月", "9月", "10月"], labels: ['6月', '7月', '8月', '9月', '10月'],
datasets: [ datasets: [
{ {
label: "家电", label: '家电',
data: [234, 278, 270, 190, 230], data: [234, 278, 270, 190, 230]
}, },
{ {
label: "百货", label: '百货',
data: [164, 178, 150, 135, 160], data: [164, 178, 150, 135, 160]
}, },
{ {
label: "食品", label: '食品',
data: [74, 118, 200, 235, 90], data: [74, 118, 200, 235, 90]
}, }
], ]
}; };
const todoList = reactive([ const todoList = reactive([
{ {
title: "今天要修复100个bug", title: '今天要修复100个bug',
status: false, status: false
}, },
{ {
title: "今天要修复100个bug", title: '今天要修复100个bug',
status: false, status: false
}, },
{ {
title: "今天要写100行代码加几个bug吧", title: '今天要写100行代码加几个bug吧',
status: false, status: false
}, },
{ {
title: "今天要修复100个bug", title: '今天要修复100个bug',
status: false, status: false
}, },
{ {
title: "今天要修复100个bug", title: '今天要修复100个bug',
status: true, status: true
}, },
{ {
title: "今天要写100行代码加几个bug吧", title: '今天要写100行代码加几个bug吧',
status: true, status: true
}, }
]); ]);
return {
name,
data,
options,
options2,
todoList,
role,
};
},
};
</script> </script>
<style scoped> <style scoped>
@ -281,7 +237,7 @@ export default {
} }
.grid-con-2 .grid-num { .grid-con-2 .grid-num {
color: rgb(45, 140, 240); color: rgb(100, 213, 114);
} }
.grid-con-3 .grid-con-icon { .grid-con-3 .grid-con-icon {
@ -300,12 +256,6 @@ export default {
margin-bottom: 20px; margin-bottom: 20px;
} }
.user-avator {
width: 120px;
height: 120px;
border-radius: 50%;
}
.user-info-cont { .user-info-cont {
padding-left: 50px; padding-left: 50px;
flex: 1; flex: 1;

View File

@ -1,26 +1,14 @@
<template> <template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-redpacket_fill"></i> 支持作者
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container"> <div class="container">
<div class="plugins-tips">如果该框架对你有帮助那就请作者喝杯饮料吧加微信号linxin_20探讨问题</div> <div class="plugins-tips">
如果该框架对你有帮助那就请作者喝杯饮料吧<el-icon><ColdDrink /></el-icon> linxin_20
</div>
<div> <div>
<img src="https://lin-xin.gitee.io/images/weixin.jpg" /> <img src="https://lin-xin.gitee.io/images/weixin.jpg" />
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script setup lang="ts" name="donate"></script>
export default {
name: "donate"
};
</script>
<style> <style></style>
</style>

View File

@ -1,37 +1,24 @@
<template> <template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-calendar"></i> 表单
</el-breadcrumb-item>
<el-breadcrumb-item>富文本编辑器</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container"> <div class="container">
<div class="plugins-tips"> <div class="plugins-tips">
wangEditor轻量级 web 富文本编辑器配置方便使用简单 wangEditor轻量级 web 富文本编辑器配置方便使用简单 访问地址
访问地址
<a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a> <a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
</div> </div>
<div class="mgb20" ref='editor'></div> <div class="mgb20" ref="editor"></div>
<el-button type="primary" @click="syncHTML"></el-button> <el-button type="primary" @click="syncHTML"></el-button>
</div> </div>
</div>
</template> </template>
<script> <script setup lang="ts" name="editor">
import WangEditor from "wangeditor"; import WangEditor from 'wangeditor';
import { ref, reactive, onMounted, onBeforeUnmount } from "vue"; import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
export default {
name: "editor",
setup() {
const editor = ref(null); const editor = ref(null);
const content = reactive({ const content = reactive({
html: "", html: '',
text: "", text: ''
}); });
let instance; let instance: any;
onMounted(() => { onMounted(() => {
instance = new WangEditor(editor.value); instance = new WangEditor(editor.value);
instance.config.zIndex = 1; instance.config.zIndex = 1;
@ -45,14 +32,6 @@ export default {
content.html = instance.txt.html(); content.html = instance.txt.html();
console.log(content.html); console.log(content.html);
}; };
return {
syncHTML,
editor,
content,
};
},
};
</script> </script>
<style> <style></style>
</style>

View File

@ -1,5 +1,4 @@
<template> <template>
<div class="about">
<v-header /> <v-header />
<v-sidebar /> <v-sidebar />
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }"> <div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
@ -8,34 +7,20 @@
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition name="move" mode="out-in"> <transition name="move" mode="out-in">
<keep-alive :include="tags.nameList"> <keep-alive :include="tags.nameList">
<component :is="Component" /> <component :is="Component"></component>
</keep-alive> </keep-alive>
</transition> </transition>
</router-view> </router-view>
<!-- <el-backtop target=".content"></el-backtop> -->
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { useSidebarStore } from '../store/sidebar' import { useSidebarStore } from '../store/sidebar';
import { useTagsStore } from '../store/tags' import { useTagsStore } from '../store/tags';
import vHeader from "../components/Header.vue"; import vHeader from '../components/header.vue';
import vSidebar from "../components/Sidebar.vue"; import vSidebar from '../components/sidebar.vue';
import vTags from "../components/Tags.vue"; import vTags from '../components/tags.vue';
export default {
components: {
vHeader,
vSidebar,
vTags,
},
setup() {
const sidebar = useSidebarStore(); const sidebar = useSidebarStore();
const tags = useTagsStore(); const tags = useTagsStore();
return {
tags,
sidebar,
};
},
};
</script> </script>

View File

@ -1,40 +0,0 @@
<template>
<section class="main">
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-global"></i>
{{$t('i18n.breadcrumb')}}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<span>{{$t('i18n.tips')}}</span>
<el-button
type="primary"
@click="$i18n.locale = $i18n.locale === 'zh-cn'?'en':'zh-cn';"
>{{$t('i18n.btn')}}</el-button>
<div class="list">
<h2>{{$t('i18n.title1')}}</h2>
<p>{{$t('i18n.p1')}}</p>
<p>{{$t('i18n.p2')}}</p>
<p>{{$t('i18n.p3')}}</p>
</div>
</div>
</section>
</template>
<script>
export default {
name: "i18n"
};
</script>
<style scoped>
.list {
padding: 30px 0;
}
.list p {
margin-bottom: 20px;
}
</style>

View File

@ -1,15 +1,9 @@
<template> <template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-emoji"></i> 自定义图标
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container"> <div class="container">
<h2>使用方法</h2> <h2>使用方法</h2>
<p style="line-height: 50px;">直接通过设置类名为 el-icon-lx-iconName 来使用即可例如{{iconList.length}}个图标</p> <p style="line-height: 50px">
直接通过设置类名为 el-icon-lx-iconName 来使用即可例如{{ iconList.length }}个图标
</p>
<p class="example-p"> <p class="example-p">
<i class="el-icon-lx-redpacket_fill" style="font-size: 30px; color: #ff5900"></i> <i class="el-icon-lx-redpacket_fill" style="font-size: 30px; color: #ff5900"></i>
<span>&lt;i class=&quot;el-icon-lx-redpacket_fill&quot;&gt;&lt;/i&gt;</span> <span>&lt;i class=&quot;el-icon-lx-redpacket_fill&quot;&gt;&lt;/i&gt;</span>
@ -36,155 +30,144 @@
</li> </li>
</ul> </ul>
</div> </div>
</div>
</template> </template>
<script> <script setup lang="ts" name="icon">
import { computed, ref } from "vue"; import { computed, ref } from 'vue';
export default {
name: "icon", const iconList: Array<string> = [
setup() { 'attentionforbid',
const iconList = [ 'attentionforbidfill',
"attentionforbid", 'attention',
"attentionforbidfill", 'attentionfill',
"attention", 'tag',
"attentionfill", 'tagfill',
"tag", 'people',
"tagfill", 'peoplefill',
"people", 'notice',
"peoplefill", 'noticefill',
"notice", 'mobile',
"noticefill", 'mobilefill',
"mobile", 'voice',
"mobilefill", 'voicefill',
"voice", 'unlock',
"voicefill", 'lock',
"unlock", 'home',
"lock", 'homefill',
"home", 'delete',
"homefill", 'deletefill',
"delete", 'notification',
"deletefill", 'notificationfill',
"notification", 'notificationforbidfill',
"notificationfill", 'like',
"notificationforbidfill", 'likefill',
"like", 'comment',
"likefill", 'commentfill',
"comment", 'camera',
"commentfill", 'camerafill',
"camera", 'warn',
"camerafill", 'warnfill',
"warn", 'time',
"warnfill", 'timefill',
"time", 'location',
"timefill", 'locationfill',
"location", 'favor',
"locationfill", 'favorfill',
"favor", 'skin',
"favorfill", 'skinfill',
"skin", 'news',
"skinfill", 'newsfill',
"news", 'record',
"newsfill", 'recordfill',
"record", 'emoji',
"recordfill", 'emojifill',
"emoji", 'message',
"emojifill", 'messagefill',
"message", 'goods',
"messagefill", 'goodsfill',
"goods", 'crown',
"goodsfill", 'crownfill',
"crown", 'move',
"crownfill", 'add',
"move", 'hot',
"add", 'hotfill',
"hot", 'service',
"hotfill", 'servicefill',
"service", 'present',
"servicefill", 'presentfill',
"present", 'pic',
"presentfill", 'picfill',
"pic", 'rank',
"picfill", 'rankfill',
"rank", 'male',
"rankfill", 'female',
"male", 'down',
"female", 'top',
"down", 'recharge',
"top", 'rechargefill',
"recharge", 'forward',
"rechargefill", 'forwardfill',
"forward", 'info',
"forwardfill", 'infofill',
"info", 'redpacket',
"infofill", 'redpacket_fill',
"redpacket", 'roundadd',
"redpacket_fill", 'roundaddfill',
"roundadd", 'friendadd',
"roundaddfill", 'friendaddfill',
"friendadd", 'cart',
"friendaddfill", 'cartfill',
"cart", 'more',
"cartfill", 'moreandroid',
"more", 'back',
"moreandroid", 'right',
"back", 'shop',
"right", 'shopfill',
"shop", 'question',
"shopfill", 'questionfill',
"question", 'roundclose',
"questionfill", 'roundclosefill',
"roundclose", 'roundcheck',
"roundclosefill", 'roundcheckfill',
"roundcheck", 'global',
"roundcheckfill", 'mail',
"global", 'punch',
"mail", 'exit',
"punch", 'upload',
"exit", 'read',
"upload", 'file',
"read", 'link',
"file", 'full',
"link", 'group',
"full", 'friend',
"group", 'profile',
"friend", 'addressbook',
"profile", 'calendar',
"addressbook", 'text',
"calendar", 'copy',
"text", 'share',
"copy", 'wifi',
"share", 'vipcard',
"wifi", 'weibo',
"vipcard", 'remind',
"weibo", 'refresh',
"remind", 'filter',
"refresh", 'settings',
"filter", 'scan',
"settings", 'qrcode',
"scan", 'cascades',
"qrcode", 'apps',
"cascades", 'sort',
"apps", 'searchlist',
"sort", 'search',
"searchlist", 'edit'
"search",
"edit",
]; ];
const keyword = ref(""); const keyword = ref('');
const list = computed(() => { const list = computed(() => {
return iconList.filter((item) => { return iconList.filter(item => {
return item.indexOf(keyword.value) !== -1; return item.indexOf(keyword.value) !== -1;
}); });
}); });
return {
iconList,
keyword,
list,
};
},
};
</script> </script>
<style scoped> <style scoped>

View File

@ -6,20 +6,24 @@
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="param.username" placeholder="username"> <el-input v-model="param.username" placeholder="username">
<template #prepend> <template #prepend>
<el-button icon="el-icon-user"></el-button> <el-button :icon="User"></el-button>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input type="password" placeholder="password" v-model="param.password" <el-input
@keyup.enter="submitForm()"> type="password"
placeholder="password"
v-model="param.password"
@keyup.enter="submitForm(login)"
>
<template #prepend> <template #prepend>
<el-button icon="el-icon-lock"></el-button> <el-button :icon="Lock"></el-button>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<div class="login-btn"> <div class="login-btn">
<el-button type="primary" @click="submitForm()"></el-button> <el-button type="primary" @click="submitForm(login)"></el-button>
</div> </div>
<p class="login-tips">Tips : 用户名和密码随便填</p> <p class="login-tips">Tips : 用户名和密码随便填</p>
</el-form> </el-form>
@ -27,41 +31,50 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { ref, reactive } from "vue"; import { ref, reactive } from 'vue';
import { useTagsStore } from '../store/tags' import { useTagsStore } from '../store/tags';
import { useRouter } from "vue-router"; import { usePermissStore } from '../store/permiss';
import { ElMessage } from "element-plus"; import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import { Lock, User } from '@element-plus/icons-vue';
interface LoginInfo {
username: string;
password: string;
}
export default {
setup() {
const router = useRouter(); const router = useRouter();
const param = reactive({ const param = reactive<LoginInfo>({
username: "admin", username: 'admin',
password: "123123", password: '123123'
}); });
const rules = { const rules: FormRules = {
username: [ username: [
{ {
required: true, required: true,
message: "请输入用户名", message: '请输入用户名',
trigger: "blur", trigger: 'blur'
}, }
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
], ],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}; };
const login = ref(null); const permiss = usePermissStore();
const submitForm = () => { const login = ref<FormInstance>();
login.value.validate((valid) => { const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) { if (valid) {
ElMessage.success("登录成功"); ElMessage.success('登录成功');
localStorage.setItem("ms_username", param.username); localStorage.setItem('ms_username', param.username);
router.push("/"); const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
permiss.handleSet(keys);
localStorage.setItem('ms_keys', JSON.stringify(keys));
router.push('/');
} else { } else {
ElMessage.error("登录成功"); ElMessage.error('登录成功');
return false; return false;
} }
}); });
@ -69,15 +82,6 @@ export default {
const tags = useTagsStore(); const tags = useTagsStore();
tags.clearTags(); tags.clearTags();
return {
param,
rules,
login,
submitForm,
};
},
};
</script> </script>
<style scoped> <style scoped>

View File

@ -1,32 +1,21 @@
<template> <template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-calendar"></i> 表单
</el-breadcrumb-item>
<el-breadcrumb-item>markdown编辑器</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container"> <div class="container">
<div class="plugins-tips"> <div class="plugins-tips">
md-editor-v3vue3版本的 markdown 编辑器配置丰富请详看文档 md-editor-v3vue3版本的 markdown 编辑器配置丰富请详看文档 访问地址
访问地址
<a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a> <a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a>
</div> </div>
<md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" /> <md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" />
<el-button type="primary">提交</el-button> <el-button type="primary">提交</el-button>
</div> </div>
</div>
</template> </template>
<script setup> <script setup lang="ts" name="md">
import { ref} from "vue"; import { ref } from 'vue';
import MdEditor from 'md-editor-v3'; import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css'; import 'md-editor-v3/lib/style.css';
const text = ref('Hello Editor!'); const text = ref('Hello Editor!');
const onUploadImg = (files)=>{ const onUploadImg = (files: any) => {
console.log(files) console.log(files);
} };
</script> </script>

View File

@ -1,40 +1,137 @@
<template> <template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-warn"></i> 权限测试
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container"> <div class="container">
<h1>管理员权限页面</h1> <div class="plugins-tips">通过 v-permiss 自定义指令实现权限管理使用非 admin 账号登录可查看效果</div>
<p>只有用 admin 账号登录的才拥有管理员权限才能进到这个页面其他账号想进来都会跳到403页面重新用管理员账号登录才有权限</p> <div class="mgb20">
<p> <span class="label">角色</span>
想尝试一下 <el-select v-model="role" @change="handleChange">
<router-link to="/login" class="logout">退出登录</router-link>便 <el-option label="超级管理员" value="admin"></el-option>
</p> <el-option label="普通用户" value="user"></el-option>
</el-select>
</div> </div>
<div class="mgb20 tree-wrapper">
<el-tree
ref="tree"
:data="data"
node-key="id"
default-expand-all
show-checkbox
:default-checked-keys="checkedKeys"
/>
</div>
<el-button type="primary" @click="onSubmit"></el-button>
</div> </div>
</template> </template>
<script> <script setup lang="ts" name="permission">
export default { import { ref } from 'vue';
name: "permission" import { ElTree } from 'element-plus';
import { usePermissStore } from '../store/permiss';
const role = ref<string>('admin');
interface Tree {
id: string;
label: string;
children?: Tree[];
}
const data: Tree[] = [
{
id: '1',
label: '系统首页'
},
{
id: '2',
label: '基础表格',
children: [
{
id: '15',
label: '编辑'
},
{
id: '16',
label: '删除'
}
]
},
{
id: '3',
label: 'tab选项卡'
},
{
id: '4',
label: '表单相关',
children: [
{
id: '5',
label: '基本表单'
},
{
id: '6',
label: '文件上传'
},
{
id: '7',
label: '三级菜单',
children: [
{
id: '8',
label: '富文本编辑器'
},
{
id: '9',
label: 'markdown编辑器'
}
]
}
]
},
{
id: '10',
label: '自定义图标'
},
{
id: '11',
label: 'schart图表'
},
{
id: '13',
label: '权限管理'
},
{
id: '14',
label: '支持作者'
}
];
const permiss = usePermissStore();
//
const checkedKeys = ref<string[]>([]);
const getPremission = () => {
//
checkedKeys.value = permiss.defaultList[role.value];
};
getPremission();
//
const tree = ref<InstanceType<typeof ElTree>>();
const onSubmit = () => {
//
console.log(tree.value!.getCheckedKeys(false));
};
const handleChange = (val: string[]) => {
tree.value!.setCheckedKeys(permiss.defaultList[role.value]);
}; };
</script> </script>
<style scoped> <style scoped>
h1 { .tree-wrapper {
text-align: center; max-width: 500px;
margin: 30px 0;
} }
p { .label {
line-height: 30px; font-size: 14px;
margin-bottom: 10px;
text-indent: 2em;
}
.logout {
color: #409eff;
} }
</style> </style>

View File

@ -1,10 +1,4 @@
<template> <template>
<div class="">
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item><i class="el-icon-lx-copy"></i> tab选项卡</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container"> <div class="container">
<el-tabs v-model="message"> <el-tabs v-model="message">
<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first"> <el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
@ -67,63 +61,49 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</div>
</template> </template>
<script> <script setup lang="ts" name="tabs">
import { ref, reactive } from "vue"; import { ref, reactive } from 'vue';
export default {
name: "tabs", const message = ref('first');
setup() {
const message = ref("first");
const state = reactive({ const state = reactive({
unread: [ unread: [
{ {
date: "2018-04-19 20:00:00", date: '2018-04-19 20:00:00',
title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护", title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
}, },
{ {
date: "2018-04-19 21:00:00", date: '2018-04-19 21:00:00',
title: "今晚12点整发大红包先到先得", title: '今晚12点整发大红包先到先得'
}, }
], ],
read: [ read: [
{ {
date: "2018-04-19 20:00:00", date: '2018-04-19 20:00:00',
title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护", title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
}, }
], ],
recycle: [ recycle: [
{ {
date: "2018-04-19 20:00:00", date: '2018-04-19 20:00:00',
title: "【系统通知】该系统将于今晚凌晨2点到5点进行升级维护", title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
}, }
], ]
}); });
const handleRead = (index) => { const handleRead = (index: number) => {
const item = state.unread.splice(index, 1); const item = state.unread.splice(index, 1);
console.log(item);
state.read = item.concat(state.read); state.read = item.concat(state.read);
}; };
const handleDel = (index) => { const handleDel = (index: number) => {
const item = state.read.splice(index, 1); const item = state.read.splice(index, 1);
state.recycle = item.concat(state.recycle); state.recycle = item.concat(state.recycle);
}; };
const handleRestore = (index) => { const handleRestore = (index: number) => {
const item = state.recycle.splice(index, 1); const item = state.recycle.splice(index, 1);
state.read = item.concat(state.read); state.read = item.concat(state.read);
}; };
return {
message,
state,
handleRead,
handleDel,
handleRestore,
};
},
};
</script> </script>
<style> <style>
@ -134,4 +114,3 @@ export default {
margin-top: 30px; margin-top: 30px;
} }
</style> </style>

View File

@ -1,22 +1,12 @@
<template> <template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-calendar"></i> 表单
</el-breadcrumb-item>
<el-breadcrumb-item>图片上传</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container"> <div class="container">
<div class="content-title">支持拖拽</div> <div class="content-title">支持拖拽</div>
<div class="plugins-tips"> <div class="plugins-tips">
Element UI自带上传组件 Element Plus自带上传组件 访问地址
访问地址 <a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>
<a href="http://element.eleme.io/#/zh-CN/component/upload" target="_blank">Element UI Upload</a>
</div> </div>
<el-upload class="upload-demo" drag action="http://jsonplaceholder.typicode.com/api/posts/" multiple> <el-upload class="upload-demo" drag action="http://jsonplaceholder.typicode.com/api/posts/" multiple>
<i class="el-icon-upload"></i> <el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text"> <div class="el-upload__text">
将文件拖到此处 将文件拖到此处
<em>点击上传</em> <em>点击上传</em>
@ -28,66 +18,13 @@
<div class="content-title">支持裁剪</div> <div class="content-title">支持裁剪</div>
<div class="plugins-tips"> <div class="plugins-tips">
vue-cropperjs一个封装了 cropperjs Vue 组件 vue-cropperjs一个封装了 cropperjs Vue 组件 访问地址
访问地址 <a href="https://github.com/Agontuk/vue-cropperjs" target="_blank">vue-cropperjs</a> 示例请查看
<a href="https://github.com/Agontuk/vue-cropperjs" target="_blank">vue-cropperjs</a> <router-link to="/user">个人中心</router-link>
</div>
</div> </div>
</div> </div>
</template> </template>
<script>
import { ref } from "vue";
import VueCropper from "vue-cropperjs";
import "cropperjs/dist/cropper.css";
import defaultSrc from "../assets/img/img.jpg";
export default {
name: "upload",
components: {
VueCropper,
},
setup() {
const imgSrc = ref("");
const cropImg = ref(defaultSrc);
const dialogVisible = ref(false);
const cropper = ref(null);
const setImage = (e) => {
const file = e.target.files[0];
if (!file.type.includes("image/")) {
return;
}
const reader = new FileReader();
reader.onload = (event) => {
dialogVisible.value = true;
imgSrc.value = event.target.result;
cropper.value && cropper.value.replace(event.target.result);
};
reader.readAsDataURL(file);
};
const cropImage = () => {
cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
};
const cancelCrop = () => {
dialogVisible.value = false;
cropImg.value = defaultSrc;
};
return {
cropper,
imgSrc,
cropImg,
dialogVisible,
setImage,
cropImage,
cancelCrop,
};
},
};
</script>
<style scoped> <style scoped>
.content-title { .content-title {
font-weight: 400; font-weight: 400;

View File

@ -10,7 +10,7 @@
</template> </template>
<div class="info"> <div class="info">
<div class="info-image" @click="showDialog"> <div class="info-image" @click="showDialog">
<img :src="avatarImg" /> <el-avatar :size="100" :src="avatarImg" />
<span class="info-edit"> <span class="info-edit">
<i class="el-icon-lx-camerafill"></i> <i class="el-icon-lx-camerafill"></i>
</span> </span>
@ -46,12 +46,19 @@
</el-col> </el-col>
</el-row> </el-row>
<el-dialog title="裁剪图片" v-model="dialogVisible" width="600px"> <el-dialog title="裁剪图片" v-model="dialogVisible" width="600px">
<vue-cropper ref="cropper" :src="imgSrc" :ready="cropImage" :zoom="cropImage" :cropmove="cropImage" <vue-cropper
style="width: 100%; height: 400px"></vue-cropper> ref="cropper"
:src="imgSrc"
:ready="cropImage"
:zoom="cropImage"
:cropmove="cropImage"
style="width: 100%; height: 400px"
></vue-cropper>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button class="crop-demo-btn" type="primary">选择图片 <el-button class="crop-demo-btn" type="primary"
>选择图片
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" /> <input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
</el-button> </el-button>
<el-button type="primary" @click="saveAvatar"></el-button> <el-button type="primary" @click="saveAvatar"></el-button>
@ -61,43 +68,38 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts" name="user">
import { reactive, ref } from "vue"; import { reactive, ref } from 'vue';
import VueCropper from "vue-cropperjs"; import VueCropper from 'vue-cropperjs';
import "cropperjs/dist/cropper.css"; import 'cropperjs/dist/cropper.css';
import avatar from "../assets/img/img.jpg"; import avatar from '../assets/img/img.jpg';
export default {
name: "user", const name = localStorage.getItem('ms_username');
components: {
VueCropper,
},
setup() {
const name = localStorage.getItem("ms_username");
const form = reactive({ const form = reactive({
old: "", old: '',
new: "", new: '',
desc: "不可能我的代码怎么可能会有bug", desc: '不可能我的代码怎么可能会有bug'
}); });
const onSubmit = () => {}; const onSubmit = () => {};
const avatarImg = ref(avatar); const avatarImg = ref(avatar);
const imgSrc = ref(""); const imgSrc = ref('');
const cropImg = ref(""); const cropImg = ref('');
const dialogVisible = ref(false); const dialogVisible = ref(false);
const cropper = ref(null); const cropper: any = ref();
const showDialog = () => { const showDialog = () => {
dialogVisible.value = true; dialogVisible.value = true;
imgSrc.value = avatarImg.value; imgSrc.value = avatarImg.value;
}; };
const setImage = (e) => { const setImage = (e: any) => {
const file = e.target.files[0]; const file = e.target.files[0];
if (!file.type.includes("image/")) { if (!file.type.includes('image/')) {
return; return;
} }
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (event) => { reader.onload = (event: any) => {
dialogVisible.value = true; dialogVisible.value = true;
imgSrc.value = event.target.result; imgSrc.value = event.target.result;
cropper.value && cropper.value.replace(event.target.result); cropper.value && cropper.value.replace(event.target.result);
@ -113,23 +115,6 @@ export default {
avatarImg.value = cropImg.value; avatarImg.value = cropImg.value;
dialogVisible.value = false; dialogVisible.value = false;
}; };
return {
name,
form,
onSubmit,
cropper,
avatarImg,
imgSrc,
cropImg,
showDialog,
dialogVisible,
setImage,
cropImage,
saveAvatar,
};
},
};
</script> </script>
<style scoped> <style scoped>
@ -147,10 +132,7 @@ export default {
border-radius: 50px; border-radius: 50px;
overflow: hidden; overflow: hidden;
} }
.info-image img {
width: 100%;
height: 100%;
}
.info-edit { .info-edit {
display: flex; display: flex;
justify-content: center; justify-content: center;

127
src/views/charts.vue Normal file
View File

@ -0,0 +1,127 @@
<template>
<div class="container">
<div class="plugins-tips">
vue-schartvue.js封装sChart.js的图表组件 访问地址
<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
</div>
<div class="schart-box">
<div class="content-title">柱状图</div>
<schart class="schart" canvasId="bar" :options="options1"></schart>
</div>
<div class="schart-box">
<div class="content-title">折线图</div>
<schart class="schart" canvasId="line" :options="options2"></schart>
</div>
<div class="schart-box">
<div class="content-title">饼状图</div>
<schart class="schart" canvasId="pie" :options="options3"></schart>
</div>
<div class="schart-box">
<div class="content-title">环形图</div>
<schart class="schart" canvasId="ring" :options="options4"></schart>
</div>
</div>
</template>
<script setup lang="ts" name="basecharts">
import Schart from 'vue-schart';
const options1 = {
type: 'bar',
title: {
text: '最近一周各品类销售图'
},
bgColor: '#fbfbfb',
labels: ['周一', '周二', '周三', '周四', '周五'],
datasets: [
{
label: '家电',
fillColor: 'rgba(241, 49, 74, 0.5)',
data: [234, 278, 270, 190, 230]
},
{
label: '百货',
data: [164, 178, 190, 135, 160]
},
{
label: '食品',
data: [144, 198, 150, 235, 120]
}
]
};
const options2 = {
type: 'line',
title: {
text: '最近几个月各品类销售趋势图'
},
bgColor: '#fbfbfb',
labels: ['6月', '7月', '8月', '9月', '10月'],
datasets: [
{
label: '家电',
data: [234, 278, 270, 190, 230]
},
{
label: '百货',
data: [164, 178, 150, 135, 160]
},
{
label: '食品',
data: [114, 138, 200, 235, 190]
}
]
};
const options3 = {
type: 'pie',
title: {
text: '服装品类销售饼状图'
},
legend: {
position: 'left'
},
bgColor: '#fbfbfb',
labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'],
datasets: [
{
data: [334, 278, 190, 235, 260, 200, 141]
}
]
};
const options4 = {
type: 'ring',
title: {
text: '环形三等分'
},
showValue: false,
legend: {
position: 'bottom',
bottom: 40
},
bgColor: '#fbfbfb',
labels: ['vue', 'react', 'angular'],
datasets: [
{
data: [500, 500, 500]
}
]
};
</script>
<style scoped>
.schart-box {
display: inline-block;
margin: 20px;
}
.schart {
width: 600px;
height: 400px;
}
.content-title {
clear: both;
font-weight: 400;
line-height: 50px;
margin: 10px 0;
font-size: 22px;
color: #1f2f3d;
}
</style>

156
src/views/form.vue Normal file
View File

@ -0,0 +1,156 @@
<template>
<div class="container">
<div class="form-box">
<el-form ref="formRef" :rules="rules" :model="form" label-width="80px">
<el-form-item label="表单名称" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="选择器" prop="region">
<el-select v-model="form.region" placeholder="请选择">
<el-option key="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-form-item prop="date1">
<el-date-picker
type="date"
placeholder="选择日期"
v-model="form.date1"
style="width: 100%"
></el-date-picker>
</el-form-item>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 100%">
</el-time-picker>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="城市级联" prop="options">
<el-cascader :options="options" v-model="form.options"></el-cascader>
</el-form-item>
<el-form-item label="选择开关" prop="delivery">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<el-form-item label="多选框" prop="type">
<el-checkbox-group v-model="form.type">
<el-checkbox label="步步高" name="type"></el-checkbox>
<el-checkbox label="小天才" name="type"></el-checkbox>
<el-checkbox label="imoo" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="单选框" prop="resource">
<el-radio-group v-model="form.resource">
<el-radio label="步步高"></el-radio>
<el-radio label="小天才"></el-radio>
<el-radio label="imoo"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="文本框" prop="desc">
<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit(formRef)"></el-button>
<el-button @click="onReset(formRef)"></el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup lang="ts" name="baseform">
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
const options = [
{
value: 'guangdong',
label: '广东省',
children: [
{
value: 'guangzhou',
label: '广州市',
children: [
{
value: 'tianhe',
label: '天河区'
},
{
value: 'haizhu',
label: '海珠区'
}
]
},
{
value: 'dongguan',
label: '东莞市',
children: [
{
value: 'changan',
label: '长安镇'
},
{
value: 'humen',
label: '虎门镇'
}
]
}
]
},
{
value: 'hunan',
label: '湖南省',
children: [
{
value: 'changsha',
label: '长沙市',
children: [
{
value: 'yuelu',
label: '岳麓区'
}
]
}
]
}
];
const rules: FormRules = {
name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }]
};
const formRef = ref<FormInstance>();
const form = reactive({
name: '',
region: '',
date1: '',
date2: '',
delivery: true,
type: ['步步高'],
resource: '小天才',
desc: '',
options: []
});
//
const onSubmit = (formEl: FormInstance | undefined) => {
//
if (!formEl) return;
formEl.validate(valid => {
if (valid) {
console.log(form);
ElMessage.success('提交成功!');
} else {
return false;
}
});
};
//
const onReset = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
</script>

191
src/views/table.vue Normal file
View File

@ -0,0 +1,191 @@
<template>
<div>
<div class="container">
<div class="handle-box">
<el-select v-model="query.address" placeholder="地址" class="handle-select mr10">
<el-option key="1" label="广东省" value="广东省"></el-option>
<el-option key="2" label="湖南省" value="湖南省"></el-option>
</el-select>
<el-input v-model="query.name" placeholder="用户名" class="handle-input mr10"></el-input>
<el-button type="primary" :icon="Search" @click="handleSearch"></el-button>
<el-button type="primary" :icon="Plus">新增</el-button>
</div>
<el-table :data="tableData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
<el-table-column prop="id" label="ID" width="55" align="center"></el-table-column>
<el-table-column prop="name" label="用户名"></el-table-column>
<el-table-column label="账户余额">
<template #default="scope">{{ scope.row.money }}</template>
</el-table-column>
<el-table-column label="头像(查看大图)" align="center">
<template #default="scope">
<el-image
class="table-td-thumb"
:src="scope.row.thumb"
:z-index="10"
:preview-src-list="[scope.row.thumb]"
preview-teleported
>
</el-image>
</template>
</el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
<el-table-column label="状态" align="center">
<template #default="scope">
<el-tag
:type="scope.row.state === '成功' ? 'success' : scope.row.state === '失败' ? 'danger' : ''"
>
{{ scope.row.state }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="date" label="注册时间"></el-table-column>
<el-table-column label="操作" width="220" align="center">
<template #default="scope">
<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)" v-permiss="15">
编辑
</el-button>
<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index)" v-permiss="16">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
background
layout="total, prev, pager, next"
:current-page="query.pageIndex"
:page-size="query.pageSize"
:total="pageTotal"
@current-change="handlePageChange"
></el-pagination>
</div>
</div>
<!-- 编辑弹出框 -->
<el-dialog title="编辑" v-model="editVisible" width="30%">
<el-form label-width="70px">
<el-form-item label="用户名">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="地址">
<el-input v-model="form.address"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="editVisible = false"> </el-button>
<el-button type="primary" @click="saveEdit"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="basetable">
import { ref, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Delete, Edit, Search, Plus } from '@element-plus/icons-vue';
import { fetchData } from '../api/index';
interface TableItem {
id: number;
name: string;
money: string;
state: string;
date: string;
address: string;
}
const query = reactive({
address: '',
name: '',
pageIndex: 1,
pageSize: 10
});
const tableData = ref<TableItem[]>([]);
const pageTotal = ref(0);
//
const getData = () => {
fetchData().then(res => {
tableData.value = res.data.list;
pageTotal.value = res.data.pageTotal || 50;
});
};
getData();
//
const handleSearch = () => {
query.pageIndex = 1;
getData();
};
//
const handlePageChange = (val: number) => {
query.pageIndex = val;
getData();
};
//
const handleDelete = (index: number) => {
//
ElMessageBox.confirm('确定要删除吗?', '提示', {
type: 'warning'
})
.then(() => {
ElMessage.success('删除成功');
tableData.value.splice(index, 1);
})
.catch(() => {});
};
//
const editVisible = ref(false);
let form = reactive({
name: '',
address: ''
});
let idx: number = -1;
const handleEdit = (index: number, row: any) => {
idx = index;
form.name = row.name;
form.address = row.address;
editVisible.value = true;
};
const saveEdit = () => {
editVisible.value = false;
ElMessage.success(`修改第 ${idx + 1} 行成功`);
tableData.value[idx].name = form.name;
tableData.value[idx].address = form.address;
};
</script>
<style scoped>
.handle-box {
margin-bottom: 20px;
}
.handle-select {
width: 120px;
}
.handle-input {
width: 300px;
}
.table {
width: 100%;
font-size: 14px;
}
.red {
color: #ff0000;
}
.mr10 {
margin-right: 10px;
}
.table-td-thumb {
display: block;
margin: auto;
width: 40px;
height: 40px;
}
</style>

10
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
declare module 'vue-schart';
declare module 'vue-cropperjs';

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts","src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

9
tsconfig.node.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -1,9 +0,0 @@
import vue from '@vitejs/plugin-vue'
export default {
base: './',
plugins: [vue()],
optimizeDeps: {
include: ['schart.js']
}
}

22
vite.config.ts Normal file
View File

@ -0,0 +1,22 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import VueSetupExtend from 'vite-plugin-vue-setup-extend';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
base: './',
plugins: [
vue(),
VueSetupExtend(),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
],
optimizeDeps: {
include: ['schart.js']
}
});