项目初始化
commit
0217700c2c
|
@ -0,0 +1,24 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["johnsoncodehk.volar"]
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Vue 3 + Typescript + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
|
||||||
|
|
||||||
|
## Type Support For `.vue` Imports in TS
|
||||||
|
|
||||||
|
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's `.vue` type support plugin by running `Volar: Switch TS Plugin on/off` from VSCode command palette.
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "oa-dxtc-vue",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^1.1.4",
|
||||||
|
"axios": "^0.26.1",
|
||||||
|
"element-plus": "^2.1.5",
|
||||||
|
"qs": "^6.10.3",
|
||||||
|
"vue": "^3.2.25",
|
||||||
|
"vue-router": "^4.0.14",
|
||||||
|
"vue-schart": "^2.0.0",
|
||||||
|
"vuex": "^4.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^2.2.0",
|
||||||
|
"typescript": "^4.5.4",
|
||||||
|
"vite": "^2.8.0",
|
||||||
|
"vue-tsc": "^0.29.8"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import "./assets/css/main.css";
|
||||||
|
@import "./assets/css/color-dark.css";
|
||||||
|
</style>
|
|
@ -0,0 +1,4 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
export const customerData = query => axios.post('http://oa-dxtc.test/customer', qs.stringify(query));
|
|
@ -0,0 +1,4 @@
|
||||||
|
import axios from 'axios'
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
export const loginData = query => axios.post('http://oa-dxtc.test/login', qs.stringify(query));
|
|
@ -0,0 +1,28 @@
|
||||||
|
.header{
|
||||||
|
background-color: #242f42;
|
||||||
|
}
|
||||||
|
.login-wrap{
|
||||||
|
background: #324157;
|
||||||
|
}
|
||||||
|
.plugins-tips{
|
||||||
|
background: #eef1f6;
|
||||||
|
}
|
||||||
|
.plugins-tips a{
|
||||||
|
color: #20a0ff;
|
||||||
|
}
|
||||||
|
.el-upload--text em {
|
||||||
|
color: #20a0ff;
|
||||||
|
}
|
||||||
|
.pure-button{
|
||||||
|
background: #20a0ff;
|
||||||
|
}
|
||||||
|
.tags-li.active {
|
||||||
|
border: 1px solid #409EFF;
|
||||||
|
background-color: #409EFF;
|
||||||
|
}
|
||||||
|
.message-title{
|
||||||
|
color: #20a0ff;
|
||||||
|
}
|
||||||
|
.collapse-btn:hover{
|
||||||
|
background: rgb(40,52,70);
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app,
|
||||||
|
.wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'PingFang SC', "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.content-box {
|
||||||
|
position: absolute;
|
||||||
|
left: 250px;
|
||||||
|
right: 0;
|
||||||
|
top: 70px;
|
||||||
|
bottom: 0;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
-webkit-transition: left .3s ease-in-out;
|
||||||
|
transition: left .3s ease-in-out;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-collapse {
|
||||||
|
left: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 30px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crumbs {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table th {
|
||||||
|
background-color: #f5f7fa !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugins-tips {
|
||||||
|
padding: 20px 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button+.el-tooltip {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table tr:hover {
|
||||||
|
background: #f6faff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgb20 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-enter-active,
|
||||||
|
.move-leave-active {
|
||||||
|
transition: opacity .1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-enter-from,
|
||||||
|
.move-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*BaseForm*/
|
||||||
|
|
||||||
|
.form-box {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-box .line {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-time-panel__content::after,
|
||||||
|
.el-time-panel__content::before {
|
||||||
|
margin-top: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Upload*/
|
||||||
|
|
||||||
|
.pure-button {
|
||||||
|
width: 150px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-core-image-corp-container .info-aside {
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload--text {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 360px;
|
||||||
|
height: 180px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload--text .el-icon-upload {
|
||||||
|
font-size: 67px;
|
||||||
|
color: #97a8be;
|
||||||
|
margin: 40px 0 16px;
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload--text {
|
||||||
|
color: #97a8be;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-upload--text em {
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*VueEditor*/
|
||||||
|
|
||||||
|
.ql-container {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-snow .ql-tooltip {
|
||||||
|
transform: translateX(117.5px) translateY(10px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-btn {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*markdown*/
|
||||||
|
|
||||||
|
.v-note-wrapper .v-note-panel {
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,142 @@
|
||||||
|
<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-sub-menu :index="item.index" :key="item.index">
|
||||||
|
<template #title>
|
||||||
|
<el-icon>
|
||||||
|
<component :is="item.icon" />
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ item.title }}</span>
|
||||||
|
</template>
|
||||||
|
<template v-for="subItem in item.subs">
|
||||||
|
<el-sub-menu v-if="subItem.subs" :index="subItem.index" :key="subItem.index">
|
||||||
|
<template #title>{{ subItem.title }}</template>
|
||||||
|
<el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">
|
||||||
|
{{ threeItem.title }}
|
||||||
|
</el-menu-item>
|
||||||
|
</el-sub-menu>
|
||||||
|
<el-menu-item v-else :index="subItem.index" :key="subItem.index">{{ subItem.title }}</el-menu-item>
|
||||||
|
</template>
|
||||||
|
</el-sub-menu>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-menu-item :index="item.index" :key="item.index">
|
||||||
|
<el-icon>
|
||||||
|
<component :is="item.icon" />
|
||||||
|
</el-icon>
|
||||||
|
<template #title>{{ item.title }}</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, watch } from "vue";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
icon: "home-filled",
|
||||||
|
index: "/dashboard",
|
||||||
|
title: "系统首页",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "user",
|
||||||
|
index: "/customer",
|
||||||
|
title: "客户",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "document-copy",
|
||||||
|
index: "/tabs",
|
||||||
|
title: "tab选项卡",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "calendar",
|
||||||
|
index: "3",
|
||||||
|
title: "表单相关",
|
||||||
|
subs: [
|
||||||
|
{
|
||||||
|
index: "/form",
|
||||||
|
title: "基本表单",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: "/upload",
|
||||||
|
title: "文件上传",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: "4",
|
||||||
|
title: "三级菜单",
|
||||||
|
subs: [
|
||||||
|
{
|
||||||
|
index: "/editor",
|
||||||
|
title: "富文本编辑器",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "pie-chart",
|
||||||
|
index: "/charts",
|
||||||
|
title: "schart图表",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "warning",
|
||||||
|
index: "7",
|
||||||
|
title: "错误处理",
|
||||||
|
subs: [
|
||||||
|
{
|
||||||
|
index: "/permission",
|
||||||
|
title: "权限测试",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: "/404",
|
||||||
|
title: "404页面",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const onRoutes = computed(() => {
|
||||||
|
return route.path;
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
const collapse = computed(() => store.state.collapse);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items,
|
||||||
|
onRoutes,
|
||||||
|
collapse,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sidebar {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 60px;
|
||||||
|
bottom: 0;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.sidebar::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
.sidebar-el-menu:not(.el-menu--collapse) {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.sidebar > ul {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,147 @@
|
||||||
|
<template>
|
||||||
|
<div class="header">
|
||||||
|
<!-- 折叠按钮 -->
|
||||||
|
<div class="collapse-btn" @click="collapseChage">
|
||||||
|
<el-icon v-if="!collapse" class="el-icon-s-fold"><expand /></el-icon>
|
||||||
|
<el-icon v-else class="el-icon-s-unfold"><fold /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="logo">后台管理系统</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<div class="header-user-con">
|
||||||
|
<!-- 用户名下拉菜单 -->
|
||||||
|
|
||||||
|
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
|
||||||
|
<span class="el-dropdown-link">
|
||||||
|
{{user.username}}
|
||||||
|
<el-icon class="el-icon-caret-bottom"><caret-bottom /></el-icon>
|
||||||
|
</span>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<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 lang="ts">
|
||||||
|
import { computed, onMounted } from "vue";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
const user = JSON.parse(localStorage.getItem("user"));
|
||||||
|
const message = 2;
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
const collapse = computed(() => store.state.collapse);
|
||||||
|
// 侧边栏折叠
|
||||||
|
const collapseChage = () => {
|
||||||
|
store.commit("handleCollapse", !collapse.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (document.body.clientWidth < 1500) {
|
||||||
|
//collapseChage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 用户名下拉菜单选择事件
|
||||||
|
const router = useRouter();
|
||||||
|
const handleCommand = (command) => {
|
||||||
|
if (command == "loginout") {
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
router.push("/login");
|
||||||
|
} else if (command == "user") {
|
||||||
|
router.push("/user");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
message,
|
||||||
|
collapse,
|
||||||
|
collapseChage,
|
||||||
|
handleCommand,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.header {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
font-size: 22px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.collapse-btn {
|
||||||
|
float: left;
|
||||||
|
padding: 0 21px;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
||||||
|
.header .logo {
|
||||||
|
float: left;
|
||||||
|
width: 250px;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
||||||
|
.header-right {
|
||||||
|
float: right;
|
||||||
|
padding-right: 50px;
|
||||||
|
}
|
||||||
|
.header-user-con {
|
||||||
|
display: flex;
|
||||||
|
height: 60px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.btn-fullscreen {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
margin-right: 5px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.btn-bell,
|
||||||
|
.btn-fullscreen {
|
||||||
|
position: relative;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.btn-bell-badge {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: -2px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #f56c6c;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btn-bell .el-icon-bell {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.user-name {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.user-avator {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
.user-avator img {
|
||||||
|
display: block;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.el-dropdown-link {
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.el-dropdown-menu__item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,183 @@
|
||||||
|
<template>
|
||||||
|
<div class="tags" v-if="showTags">
|
||||||
|
<ul>
|
||||||
|
<li class="tags-li" v-for="(item,index) in tagsList" :class="{'active': isActive(item.path)}" :key="index">
|
||||||
|
<router-link :to="item.path" class="tags-li-title">{{item.title}}</router-link>
|
||||||
|
<span class="tags-li-icon" @click="closeTags(index)">
|
||||||
|
<i class="el-icon-close"></i>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tags-close-box">
|
||||||
|
<el-dropdown @command="handleTags">
|
||||||
|
<el-button size="mini" type="primary">
|
||||||
|
标签选项
|
||||||
|
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu size="small">
|
||||||
|
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const isActive = (path) => {
|
||||||
|
return path === route.fullPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
const tagsList = computed(() => store.state.tagsList);
|
||||||
|
const showTags = computed(() => tagsList.value.length > 0);
|
||||||
|
|
||||||
|
// 关闭单个标签
|
||||||
|
const closeTags = (index) => {
|
||||||
|
const delItem = tagsList.value[index];
|
||||||
|
store.commit("delTagsItem", { index });
|
||||||
|
const item = tagsList.value[index]
|
||||||
|
? tagsList.value[index]
|
||||||
|
: tagsList.value[index - 1];
|
||||||
|
if (item) {
|
||||||
|
delItem.path === route.fullPath && router.push(item.path);
|
||||||
|
} else {
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置标签
|
||||||
|
const setTags = (route) => {
|
||||||
|
const isExist = tagsList.value.some((item) => {
|
||||||
|
return item.path === route.fullPath;
|
||||||
|
});
|
||||||
|
if (!isExist) {
|
||||||
|
if (tagsList.value.length >= 8) {
|
||||||
|
store.commit("delTagsItem", { index: 0 });
|
||||||
|
}
|
||||||
|
store.commit("setTagsItem", {
|
||||||
|
name: route.name,
|
||||||
|
title: route.meta.title,
|
||||||
|
path: route.fullPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setTags(route);
|
||||||
|
onBeforeRouteUpdate((to) => {
|
||||||
|
setTags(to);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 关闭全部标签
|
||||||
|
const closeAll = () => {
|
||||||
|
store.commit("clearTags");
|
||||||
|
router.push("/");
|
||||||
|
};
|
||||||
|
// 关闭其他标签
|
||||||
|
const closeOther = () => {
|
||||||
|
const curItem = tagsList.value.filter((item) => {
|
||||||
|
return item.path === route.fullPath;
|
||||||
|
});
|
||||||
|
store.commit("closeTagsOther", curItem);
|
||||||
|
};
|
||||||
|
const handleTags = (command) => {
|
||||||
|
command === "other" ? closeOther() : closeAll();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭当前页面的标签页
|
||||||
|
// store.commit("closeCurrentTag", {
|
||||||
|
// $router: router,
|
||||||
|
// $route: route
|
||||||
|
// });
|
||||||
|
|
||||||
|
return {
|
||||||
|
isActive,
|
||||||
|
tagsList,
|
||||||
|
showTags,
|
||||||
|
closeTags,
|
||||||
|
handleTags,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tags {
|
||||||
|
position: relative;
|
||||||
|
height: 30px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
padding-right: 120px;
|
||||||
|
box-shadow: 0 5px 10px #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags ul {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-li {
|
||||||
|
float: left;
|
||||||
|
margin: 3px 5px 2px 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 23px;
|
||||||
|
line-height: 23px;
|
||||||
|
border: 1px solid #e9eaec;
|
||||||
|
background: #fff;
|
||||||
|
padding: 0 5px 0 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
color: #666;
|
||||||
|
-webkit-transition: all 0.3s ease-in;
|
||||||
|
-moz-transition: all 0.3s ease-in;
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-li:not(.active):hover {
|
||||||
|
background: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-li.active {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-li-title {
|
||||||
|
float: left;
|
||||||
|
max-width: 80px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-right: 5px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-li.active .tags-li-title {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-close-box {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-top: 1px;
|
||||||
|
text-align: center;
|
||||||
|
width: 110px;
|
||||||
|
height: 30px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,8 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||||
|
|
||||||
|
import * as ElIcons from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import store from './store'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
for (const name in ElIcons){
|
||||||
|
app.component(name,(ElIcons as any)[name])
|
||||||
|
}
|
||||||
|
app.use(ElementPlus, {
|
||||||
|
locale: zhCn
|
||||||
|
})
|
||||||
|
.use(store)
|
||||||
|
.use(router)
|
||||||
|
.mount('#app')
|
|
@ -0,0 +1,148 @@
|
||||||
|
import {createRouter, createWebHashHistory} from "vue-router";
|
||||||
|
import Home from "../views/Home.vue";
|
||||||
|
import Dashboard from "../views/Dashboard.vue"
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: '/dashboard'
|
||||||
|
}, {
|
||||||
|
path: "/",
|
||||||
|
name: "Home",
|
||||||
|
component: Home,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/dashboard",
|
||||||
|
name: "dashboard",
|
||||||
|
meta: {
|
||||||
|
title: '系统首页'
|
||||||
|
},
|
||||||
|
//component: () => import ( /* webpackChunkName: "dashboard" */ "../views/Dashboard.vue")
|
||||||
|
component :Dashboard
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/customer",
|
||||||
|
name: "basetable",
|
||||||
|
meta: {
|
||||||
|
title: '客户'
|
||||||
|
},
|
||||||
|
component: () => import ( /* webpackChunkName: "table" */ "../views/Customer.vue")
|
||||||
|
},
|
||||||
|
//{
|
||||||
|
// path: "/charts",
|
||||||
|
// name: "basecharts",
|
||||||
|
// meta: {
|
||||||
|
// title: '图表'
|
||||||
|
// },
|
||||||
|
// component: () => import ( /* webpackChunkName: "charts" */ "../views/BaseCharts.vue")
|
||||||
|
// }, {
|
||||||
|
// path: "/form",
|
||||||
|
// name: "baseform",
|
||||||
|
// meta: {
|
||||||
|
// title: '表单'
|
||||||
|
// },
|
||||||
|
// component: () => import ( /* webpackChunkName: "form" */ "../views/BaseForm.vue")
|
||||||
|
// }, {
|
||||||
|
// path: "/tabs",
|
||||||
|
// name: "tabs",
|
||||||
|
// meta: {
|
||||||
|
// title: 'tab标签'
|
||||||
|
// },
|
||||||
|
// component: () => import ( /* webpackChunkName: "tabs" */ "../views/Tabs.vue")
|
||||||
|
// }, {
|
||||||
|
// path: "/donate",
|
||||||
|
// name: "donate",
|
||||||
|
// meta: {
|
||||||
|
// title: '鼓励作者'
|
||||||
|
// },
|
||||||
|
// component: () => import ( /* webpackChunkName: "donate" */ "../views/Donate.vue")
|
||||||
|
// }, {
|
||||||
|
// path: "/permission",
|
||||||
|
// name: "permission",
|
||||||
|
// meta: {
|
||||||
|
// title: '权限管理',
|
||||||
|
// permission: true
|
||||||
|
// },
|
||||||
|
// component: () => import ( /* webpackChunkName: "permission" */ "../views/Permission.vue")
|
||||||
|
// }, {
|
||||||
|
// path: "/i18n",
|
||||||
|
// name: "i18n",
|
||||||
|
// meta: {
|
||||||
|
// title: '国际化语言'
|
||||||
|
// },
|
||||||
|
// component: () => import ( /* webpackChunkName: "i18n" */ "../views/I18n.vue")
|
||||||
|
// }, {
|
||||||
|
// path: "/upload",
|
||||||
|
// name: "upload",
|
||||||
|
// meta: {
|
||||||
|
// title: '上传插件'
|
||||||
|
// },
|
||||||
|
// component: () => import ( /* webpackChunkName: "upload" */ "../views/Upload.vue")
|
||||||
|
// }, {
|
||||||
|
// path: "/icon",
|
||||||
|
// name: "icon",
|
||||||
|
// meta: {
|
||||||
|
// title: '自定义图标'
|
||||||
|
// },
|
||||||
|
// 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',
|
||||||
|
// name: 'user',
|
||||||
|
// meta: {
|
||||||
|
// title: '个人中心'
|
||||||
|
// },
|
||||||
|
// component: () => import (/* webpackChunkName: "user" */ '../views/User.vue')
|
||||||
|
// }, {
|
||||||
|
// path: '/editor',
|
||||||
|
// name: 'editor',
|
||||||
|
// meta: {
|
||||||
|
// title: '富文本编辑器'
|
||||||
|
// },
|
||||||
|
// component: () => import (/* webpackChunkName: "editor" */ '../views/Editor.vue')
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
path: "/login",
|
||||||
|
name: "Login",
|
||||||
|
meta: {
|
||||||
|
title: '登录'
|
||||||
|
},
|
||||||
|
component: () => import ( /* webpackChunkName: "login" */ "../views/Login.vue")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
document.title = `${to.meta.title} | vue-manage-system`;
|
||||||
|
const role = localStorage.getItem('ms_username');
|
||||||
|
if (!role && to.path !== '/login') {
|
||||||
|
next('/login');
|
||||||
|
} else if (to.meta.permission) {
|
||||||
|
// 如果是管理员权限则可进入,这里只是简单的模拟管理员权限而已
|
||||||
|
role === 'admin'
|
||||||
|
? next()
|
||||||
|
: next('/403');
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -0,0 +1,56 @@
|
||||||
|
import {createStore} from 'vuex'
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
state: {
|
||||||
|
tagsList: [],
|
||||||
|
collapse: false
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
delTagsItem(state, data) {
|
||||||
|
state
|
||||||
|
.tagsList
|
||||||
|
.splice(data.index, 1);
|
||||||
|
},
|
||||||
|
setTagsItem(state, data) {
|
||||||
|
state
|
||||||
|
.tagsList
|
||||||
|
.push(data)
|
||||||
|
},
|
||||||
|
clearTags(state) {
|
||||||
|
state.tagsList = []
|
||||||
|
},
|
||||||
|
closeTagsOther(state, data) {
|
||||||
|
state.tagsList = data;
|
||||||
|
},
|
||||||
|
closeCurrentTag(state, data) {
|
||||||
|
for (let i = 0, len = state.tagsList.length; i < len; i++) {
|
||||||
|
const item = state.tagsList[i];
|
||||||
|
if (item.path === data.$route.fullPath) {
|
||||||
|
if (i < len - 1) {
|
||||||
|
data
|
||||||
|
.$router
|
||||||
|
.push(state.tagsList[i + 1].path);
|
||||||
|
} else if (i > 0) {
|
||||||
|
data
|
||||||
|
.$router
|
||||||
|
.push(state.tagsList[i - 1].path);
|
||||||
|
} else {
|
||||||
|
data
|
||||||
|
.$router
|
||||||
|
.push("/");
|
||||||
|
}
|
||||||
|
state
|
||||||
|
.tagsList
|
||||||
|
.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 侧边栏折叠
|
||||||
|
handleCollapse(state, data) {
|
||||||
|
state.collapse = data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
modules: {}
|
||||||
|
})
|
|
@ -0,0 +1,201 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="crumbs">
|
||||||
|
<el-breadcrumb separator="/">
|
||||||
|
<el-breadcrumb-item>
|
||||||
|
<i class="el-icon-lx-cascades"></i> 客户列表
|
||||||
|
</el-breadcrumb-item>
|
||||||
|
<el-button type="primary">
|
||||||
|
<el-icon :size="20"><plus /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</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 { customerData } from "../api/customer";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "basetable",
|
||||||
|
setup() {
|
||||||
|
const query = reactive({
|
||||||
|
address: "",
|
||||||
|
name: "",
|
||||||
|
pageIndex: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
const tableData = ref([]);
|
||||||
|
const pageTotal = ref(0);
|
||||||
|
// 获取表格数据
|
||||||
|
const getData = () => {
|
||||||
|
customerData(query).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
//tableData.value = res.data;
|
||||||
|
//pageTotal.value = res.pageTotal || 0;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
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>
|
|
@ -0,0 +1,348 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" class="mgb20" style="height:252px;">
|
||||||
|
<div class="user-info">
|
||||||
|
<!-- <img src="../assets/img/img.jpg" class="user-avator" alt /> -->
|
||||||
|
<div class="user-info-cont">
|
||||||
|
<div class="user-info-name">{{ name }}</div>
|
||||||
|
<div>{{ role }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-info-list">
|
||||||
|
上次登录时间:
|
||||||
|
<span>2019-11-01</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-info-list">
|
||||||
|
上次登录地点:
|
||||||
|
<span>东莞</span>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="hover" style="height:252px;">
|
||||||
|
<template #header>
|
||||||
|
<div class="clearfix">
|
||||||
|
<span>语言详情</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
Vue
|
||||||
|
<el-progress :percentage="71.3" color="#42b983"></el-progress>JavaScript
|
||||||
|
<el-progress :percentage="24.1" color="#f1e05a"></el-progress>CSS
|
||||||
|
<el-progress :percentage="13.7"></el-progress>HTML
|
||||||
|
<el-progress :percentage="5.9" color="#f56c6c"></el-progress>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-row :gutter="20" class="mgb20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
|
<div class="grid-content grid-con-1">
|
||||||
|
<i class="el-icon-user-solid grid-con-icon"></i>
|
||||||
|
<div class="grid-cont-right">
|
||||||
|
<div class="grid-num">1234</div>
|
||||||
|
<div>用户访问量</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
|
<div class="grid-content grid-con-2">
|
||||||
|
<i class="el-icon-message-solid grid-con-icon"></i>
|
||||||
|
<div class="grid-cont-right">
|
||||||
|
<div class="grid-num">321</div>
|
||||||
|
<div>系统消息</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
|
<div class="grid-content grid-con-3">
|
||||||
|
<i class="el-icon-s-goods grid-con-icon"></i>
|
||||||
|
<div class="grid-cont-right">
|
||||||
|
<div class="grid-num">5000</div>
|
||||||
|
<div>数量</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-card shadow="hover" style="height:403px;">
|
||||||
|
<template #header>
|
||||||
|
<div class="clearfix">
|
||||||
|
<span>待办事项</span>
|
||||||
|
<el-button style="float: right; padding: 3px 0" type="text">添加</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-table :show-header="false" :data="todoList" style="width:100%;">
|
||||||
|
<el-table-column width="40">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-checkbox v-model="scope.row.status"></el-checkbox>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column>
|
||||||
|
<template #default="scope">
|
||||||
|
<div class="todo-item" :class="{
|
||||||
|
'todo-item-del': scope.row.status,
|
||||||
|
}">{{ scope.row.title }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column width="60">
|
||||||
|
<template>
|
||||||
|
<i class="el-icon-edit"></i>
|
||||||
|
<i class="el-icon-delete"></i>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card shadow="hover">
|
||||||
|
<!-- <schart ref="bar" class="schart" canvasId="bar" :options="options"></schart> -->
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card shadow="hover">
|
||||||
|
<!-- <schart ref="line" class="schart" canvasId="line" :options="options2"></schart> -->
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
//import Schart from "vue-schart";
|
||||||
|
import { reactive } from "vue";
|
||||||
|
export default {
|
||||||
|
name: "dashboard",
|
||||||
|
//components: { Schart },
|
||||||
|
setup() {
|
||||||
|
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 = {
|
||||||
|
type: "bar",
|
||||||
|
title: {
|
||||||
|
text: "最近一周各品类销售图",
|
||||||
|
},
|
||||||
|
xRorate: 25,
|
||||||
|
labels: ["周一", "周二", "周三", "周四", "周五"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "家电",
|
||||||
|
data: [234, 278, 270, 190, 230],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "百货",
|
||||||
|
data: [164, 178, 190, 135, 160],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "食品",
|
||||||
|
data: [144, 198, 150, 235, 120],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const options2 = {
|
||||||
|
type: "line",
|
||||||
|
title: {
|
||||||
|
text: "最近几个月各品类销售趋势图",
|
||||||
|
},
|
||||||
|
labels: ["6月", "7月", "8月", "9月", "10月"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "家电",
|
||||||
|
data: [234, 278, 270, 190, 230],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "百货",
|
||||||
|
data: [164, 178, 150, 135, 160],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "食品",
|
||||||
|
data: [74, 118, 200, 235, 90],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const todoList = reactive([
|
||||||
|
{
|
||||||
|
title: "今天要修复100个bug",
|
||||||
|
status: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "今天要修复100个bug",
|
||||||
|
status: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "今天要写100行代码加几个bug吧",
|
||||||
|
status: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "今天要修复100个bug",
|
||||||
|
status: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "今天要修复100个bug",
|
||||||
|
status: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "今天要写100行代码加几个bug吧",
|
||||||
|
status: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
data,
|
||||||
|
options,
|
||||||
|
options2,
|
||||||
|
todoList,
|
||||||
|
role,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.el-row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-cont-right {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-num {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-icon {
|
||||||
|
font-size: 50px;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 100px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-1 .grid-con-icon {
|
||||||
|
background: rgb(45, 140, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-1 .grid-num {
|
||||||
|
color: rgb(45, 140, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-2 .grid-con-icon {
|
||||||
|
background: rgb(100, 213, 114);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-2 .grid-num {
|
||||||
|
color: rgb(45, 140, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-3 .grid-con-icon {
|
||||||
|
background: rgb(242, 94, 67);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-con-3 .grid-num {
|
||||||
|
color: rgb(242, 94, 67);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 2px solid #ccc;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avator {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-cont {
|
||||||
|
padding-left: 50px;
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-cont div:first-child {
|
||||||
|
font-size: 30px;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-list {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
line-height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-list span {
|
||||||
|
margin-left: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mgb20 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-item {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-item-del {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schart {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<div class="common-layout" style="border:1px solid blue;">
|
||||||
|
<el-container>
|
||||||
|
<el-header>
|
||||||
|
<v-header />
|
||||||
|
</el-header>
|
||||||
|
<el-container>
|
||||||
|
<el-aside width="200px">
|
||||||
|
<v-aside />
|
||||||
|
</el-aside>
|
||||||
|
<el-main>
|
||||||
|
<v-tags />
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition name="move" mode="out-in">
|
||||||
|
<keep-alive :include="tagsList">
|
||||||
|
<component :is="Component" />
|
||||||
|
</keep-alive>
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import vHeader from '../components/Header.vue'
|
||||||
|
import vAside from '../components/Aside.vue'
|
||||||
|
import vTags from "../components/Tags.vue";
|
||||||
|
export default {
|
||||||
|
components:{
|
||||||
|
vHeader,
|
||||||
|
vAside,
|
||||||
|
vTags
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
const tagsList = computed(() =>
|
||||||
|
store.state.tagsList.map((item) => item.name)
|
||||||
|
);
|
||||||
|
const collapse = computed(() => store.state.collapse);
|
||||||
|
return {
|
||||||
|
tagsList,
|
||||||
|
collapse,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.el-header{
|
||||||
|
--el-header-padding:0
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,134 @@
|
||||||
|
<template>
|
||||||
|
<div class="login-wrap">
|
||||||
|
<div class="ms-login">
|
||||||
|
<div class="ms-title">后台管理系统</div>
|
||||||
|
<el-form :model="param" :rules="rules" ref="login" label-width="0px" class="ms-content">
|
||||||
|
<el-form-item prop="username">
|
||||||
|
<el-input v-model="param.username" placeholder="username">
|
||||||
|
<template #prepend>
|
||||||
|
<el-button icon="el-icon-user"></el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input type="password" placeholder="password" v-model="param.password"
|
||||||
|
@keyup.enter="submitForm()">
|
||||||
|
<template #prepend>
|
||||||
|
<el-button icon="el-icon-lock"></el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<div class="login-btn">
|
||||||
|
<el-button type="primary" @click="submitForm()">登录</el-button>
|
||||||
|
</div>
|
||||||
|
<p class="login-tips">Tips : 用户名和密码随便填。</p>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { loginData } from "../api/login";
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
const router = useRouter();
|
||||||
|
const param = reactive({
|
||||||
|
username: "admin",
|
||||||
|
password: "123456",
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
username: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入用户名",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const login = ref(null);
|
||||||
|
const submitForm = () => {
|
||||||
|
login.value.validate((valid) => {
|
||||||
|
if(!valid){
|
||||||
|
ElMessage.error('登录失败');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log(router)
|
||||||
|
loginData(param).then((res) => {
|
||||||
|
if(res.data.code == 0){
|
||||||
|
console.log(res.data.data);
|
||||||
|
ElMessage.success("登录成功");
|
||||||
|
localStorage.setItem("user", JSON.stringify(res.data.data));
|
||||||
|
localStorage.setItem('ms_username', res.data.data.username);
|
||||||
|
router.push("/");
|
||||||
|
}else{
|
||||||
|
ElMessage.error(res.data.msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
store.commit("clearTags");
|
||||||
|
|
||||||
|
return {
|
||||||
|
param,
|
||||||
|
rules,
|
||||||
|
login,
|
||||||
|
submitForm,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/*background-image: url(../assets/img/login-bg.jpg);*/
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
.ms-title {
|
||||||
|
width: 100%;
|
||||||
|
line-height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #fff;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
.ms-login {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 350px;
|
||||||
|
margin: -190px 0 0 -175px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.ms-content {
|
||||||
|
padding: 30px 30px;
|
||||||
|
}
|
||||||
|
.login-btn {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.login-btn button {
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.login-tips {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 30px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["esnext", "dom"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()]
|
||||||
|
})
|
Loading…
Reference in New Issue