项目初始化

master
milo 2022-03-31 09:51:47 +08:00
commit 0217700c2c
27 changed files with 4874 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -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?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["johnsoncodehk.volar"]
}

11
README.md Normal file
View File

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

13
index.html Normal file
View File

@ -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>

3099
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
package.json Normal file
View File

@ -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"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

12
src/App.vue Normal file
View File

@ -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>

4
src/api/customer.ts Normal file
View File

@ -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));

4
src/api/login.ts Normal file
View File

@ -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));

View File

@ -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);
}

177
src/assets/css/main.css Normal file
View File

@ -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;
}

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

142
src/components/Aside.vue Normal file
View File

@ -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>

147
src/components/Header.vue Normal file
View File

@ -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>

183
src/components/Tags.vue Normal file
View File

@ -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>

8
src/env.d.ts vendored Normal file
View File

@ -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
}

21
src/main.ts Normal file
View File

@ -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')

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

@ -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;

56
src/store/index.ts Normal file
View File

@ -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: {}
})

201
src/views/Customer.vue Normal file
View File

@ -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>

348
src/views/Dashboard.vue Normal file
View File

@ -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>

54
src/views/Home.vue Normal file
View File

@ -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>

134
src/views/Login.vue Normal file
View File

@ -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>

16
tsconfig.json Normal file
View File

@ -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" }]
}

8
tsconfig.node.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}

7
vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()]
})