管理端web端提交

This commit is contained in:
aixianling
2023-01-03 11:45:38 +08:00
parent d1f2a42f1c
commit bf37c7ef10
23 changed files with 926 additions and 0 deletions

7
web/README.md Normal file
View File

@@ -0,0 +1,7 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 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
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)

13
web/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>管理平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

23
web/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "wsr-web",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^1.1.4",
"axios": "^0.26.1",
"element-plus": "^2.1.9",
"pinia": "^2.0.13",
"sass": "^1.50.0",
"vue": "^3.2.25",
"vue-router": "^4.0.14"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.1",
"vite": "^2.9.2"
}
}

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

36
web/src/App.vue Normal file
View File

@@ -0,0 +1,36 @@
<template>
<router-view/>
</template>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: 100vh;
width: 100vw;
--el-color-primary: #384388;
--background-color: #F2F6FF;
}
body {
margin: 0;
}
.fill {
flex: 1;
min-width: 0;
min-height: 0;
}
.color-a2v1c5 {
color: #A2B1C5;
}
.mar-r30 {
margin-right: 30px;
}
.pad-w10 {
padding: 0 10px;
}
</style>

View File

@@ -0,0 +1,14 @@
<template>
</template>
<script>
export default {
name: "AppUserInfo",
label: "个人中心"
}
</script>
<style scoped>
</style>

BIN
web/src/assets/building.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

114
web/src/assets/iconfont.css Normal file
View File

@@ -0,0 +1,114 @@
@font-face {
font-family: "iconfont"; /* Project id 3335619 */
src: url('//at.alicdn.com/t/font_3335619_ki0rpko4inh.woff2?t=1650462455599') format('woff2'),
url('//at.alicdn.com/t/font_3335619_ki0rpko4inh.woff?t=1650462455599') format('woff'),
url('//at.alicdn.com/t/font_3335619_ki0rpko4inh.ttf?t=1650462455599') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-tishi:before {
content: "\e74c";
}
.icon-fenxiang:before {
content: "\e74d";
}
.icon-shujuyuanguanli:before {
content: "\e74b";
}
.icon-dapingguanli:before {
content: "\e74a";
}
.icon-dangzuzhi:before {
content: "\e749";
}
.icon-fanhuishangyiye:before {
content: "\e748";
}
.icon-jiediancaozuo:before {
content: "\e747";
}
.icon-shujujicheng:before {
content: "\e745";
}
.icon-shujuqingxi:before {
content: "\e746";
}
.icon-shang:before {
content: "\e743";
}
.icon-xia:before {
content: "\e744";
}
.icon-toubutouxiang:before {
content: "\e742";
}
.icon-anniusousuo:before {
content: "\e741";
}
.icon-sousuo:before {
content: "\e740";
}
.icon-daochu:before {
content: "\e73e";
}
.icon-shanchu:before {
content: "\e73f";
}
.icon-zhuantishujuku:before {
content: "\e73c";
}
.icon-jichushujuku:before {
content: "\e73d";
}
.icon-ruanjianxinxicaiji:before {
content: "\e73a";
}
.icon-yingjianxinxicaiji:before {
content: "\e73b";
}
.icon-quanxianguanli:before {
content: "\e733";
}
.icon-dangqianweizhi:before {
content: "\e734";
}
.icon-tuichu:before {
content: "\e735";
}
.icon-zuzhiguanli:before {
content: "\e736";
}
.icon-zhanghuguanli:before {
content: "\e737";
}

View File

@@ -0,0 +1,36 @@
<template>
<section class="KuDialog" v-if="show">
<div class="modal" v-if="modal"/>
</section>
</template>
<script>
export default {
name: "KuDialog",
props: {
show: Boolean,
modal: {type: Boolean, default: true}
},
data() {
return {}
},
methods: {},
created() {
}
}
</script>
<style lang="scss" scoped>
.KuDialog {
position: fixed;
z-index: 6800;
.modal {
position: fixed;
background: rgba(#000, .2);
z-index: 6700;
}
}
</style>

View File

@@ -0,0 +1,135 @@
<template>
<section class="KuLogin">
<div class="panel" :class="{border,changePwd}">
<h2 v-text="title"/>
<div class="color-a2v1c5 subTitle" v-text="`请输入您的用户名及密码`"/>
<el-form :model="form" :rules="rules" ref="LoginForm" size="large">
<el-form-item prop="name">
<el-input placeholder="用户名或手机号" v-model="form.name" clearable>
<template slot="prefix">
<el-icon :size="20" color="var(--el-color-primary)" class="inputIcon">
<user-filled/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input placeholder="请输入密码" type="password" v-model="form.password" clearable show-password>
<template slot="prefix">
<el-icon :size="20" color="var(--el-color-primary)" class="inputIcon">
<lock/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<div class="btn">登录</div>
<el-row justify="end">
<el-button class="rightBtn" type="text" @click="changePwd=true">修改密码</el-button>
</el-row>
</el-form-item>
</el-form>
</div>
<change-pwd v-bind="$props" @back="changePwd=false"/>
</section>
</template>
<script>
import {Lock, UserFilled} from "@element-plus/icons-vue";
import ChangePwd from "./changePwd";
export default {
name: "KuLogin",
components: {ChangePwd, UserFilled, Lock},
props: {
title: {default: "登录组件"},
border: Boolean
},
data() {
return {
form: {},
rules: {},
changePwd: false
}
}
}
</script>
<style lang="scss" scoped>
.KuLogin {
width: 630px;
overflow: hidden;
display: flex;
align-items: center;
padding: 20px 16px;
:deep(.panel) {
width: inherit;
padding: 30px 50px;
box-sizing: border-box;
text-align: center;
margin-bottom: 1px;
flex-shrink: 0;
margin-right: 30px;
transition: margin-left 1s ease;
&.border {
box-shadow: 0 6px 12px 1px rgba(38, 127, 255, 0.1);
border-radius: 4px 4px 4px 4px;
background: #FFFFFF;
}
&.changePwd {
margin-left: -660px;
}
h2 {
color: #31507D;
font-size: 32px;
font-family: PingFang SC-Bold, PingFang SC;
line-height: 38px;
}
.subTitle {
margin-bottom: 60px;
}
.btn {
text-align: center;
height: 60px;
line-height: 60px;
width: 100%;
color: #fff;
background: var(--el-color-primary);
border-radius: 4px 4px 4px 4px;
cursor: pointer;
&:hover {
opacity: .8;
}
}
.el-input {
.el-input__inner {
line-height: 60px;
height: 60px;
padding-left: 44px !important;
}
.el-input__prefix-inner {
align-items: center;
}
}
.rightBtn {
float: right;
}
.el-row {
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,98 @@
<template>
<div class="panel" :class="{border}">
<h2>
<el-icon :size="32" @click="next?next=false:$emit('back')">
<back/>
</el-icon>
{{ ['修改密码', next ? '新密码提交' : '原密码确认'].join('-') }}
</h2>
<el-form :model="form" :rules="rules" ref="LoginForm" size="large">
<template v-if="!next">
<el-form-item prop="name">
<el-input placeholder="用户名或手机号" v-model="form.name" clearable>
<template slot="prefix">
<el-icon :size="20" color="var(--el-color-primary)" class="inputIcon">
<user-filled/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input placeholder="请输入密码" type="password" v-model="form.password" clearable show-password>
<template slot="prefix">
<el-icon :size="20" color="var(--el-color-primary)" class="inputIcon">
<lock/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<div class="btn" @click="next=true">下一步</div>
</el-form-item>
</template>
<template v-else>
<el-form-item prop="password">
<el-input placeholder="请输入新密码" type="password" v-model="form.password" clearable show-password>
<template slot="prefix">
<el-icon :size="20" color="var(--el-color-primary)" class="inputIcon">
<lock/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input placeholder="请确认新密码" type="password" v-model="form.password" clearable show-password>
<template slot="prefix">
<el-icon :size="20" color="var(--el-color-primary)" class="inputIcon">
<lock/>
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<div class="btn" @click="submit">提交</div>
</el-form-item>
</template>
</el-form>
</div>
</template>
<script>
import {Back, Lock, UserFilled} from "@element-plus/icons-vue";
export default {
name: "changePwd",
components: {Back, UserFilled, Lock},
props: {
title: {default: "登录组件"},
border: Boolean
},
data() {
return {
form: {},
rules: {},
next: false
}
},
methods: {
submit() {
}
}
}
</script>
<style lang="scss" scoped>
h2 {
position: relative;
display: flex;
align-items: center;
justify-content: center;
.el-icon {
position: absolute;
left: 0;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<section class="building">
<div class="color-a2v1c5" v-text="'应用正在开发中...'"/>
</section>
</template>
<style lang="scss" scoped>
.building {
height: 100%;
background-image: url("../assets/building.png");
background-repeat: no-repeat;
background-position: center;
background-size: 400px 330px;
& > div {
margin-top: 50%;
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<section class="sliderNav">
<div class="item" v-for="menu in menus" :key="menu.id">
<el-row class="pad-w10" align="middle" @click="menu.expand=!menu.expand">
<i class="iconfont icon-shujuqingxi"/>
<div class="fill" v-text="menu.label"/>
<el-icon :size="20">
<component :is="arrow(menu.expand)"/>
</el-icon>
</el-row>
<template v-if="menu.expand">
<div class="app" v-for="app in menu.children" :key="app.id" v-text="app.label" :class="{active:app.id==12}"/>
</template>
</div>
</section>
</template>
<script>
import {ArrowDownBold, ArrowUpBold, Histogram} from "@element-plus/icons-vue";
export default {
name: "sliderNav",
components: {Histogram, ArrowUpBold, ArrowDownBold},
data() {
return {
menus: [
{
id: 1,
label: "基础数据库",
icon: "",
expand: false,
children: [{id: 11, label: "人口数据"}, {id: 12, label: "党员数据"}, {id: 13, label: "党组织数据"}]
},
{
id: 2,
label: "专题数据库",
icon: "",
expand: false,
children: [{id: 14, label: "办公考勤数据"}, {id: 15, label: "信访数据"}, {id: 21, label: "矛盾调解数据"}]
},
]
}
},
methods: {
arrow(expand) {
return expand ? ArrowUpBold : ArrowDownBold
}
}
}
</script>
<style lang="scss" scoped>
.sliderNav {
width: 200px;
flex-shrink: 0;
background: var(--background-color);
font-family: PingFang SC-Regular, PingFang SC;
.item {
color: var(--el-color-primary);
margin-bottom: 20px;
line-height: 50px;
cursor: pointer;
.fill {
margin-left: 12px;
}
}
.app {
padding-left: 28px;
cursor: pointer;
margin: 0 10px 0;
&:hover {
color: rgba(#fff, .8);
}
&.active {
color: #fff;
background: var(--el-color-primary);
border-radius: 4px 4px 4px 4px;
}
}
}
</style>

24
web/src/main.js Normal file
View File

@@ -0,0 +1,24 @@
import {createApp} from 'vue'
import App from './App.vue'
import router from './utils/router'
import {createPinia} from "pinia/dist/pinia";
import axios from "./utils/axios";
import tools from "./utils/tools";
import 'element-plus/dist/index.css'
import eui from 'element-plus'
import zhCn from "element-plus/es/locale/lang/zh-cn";
import "./assets/iconfont.css"
const app = createApp(App)
app.config.globalProperties.$http = axios
app.config.globalProperties.$sys = {
title: "综合大数据平台中心"
}
Object.keys(tools).map(e => app.config.globalProperties[e] = tools[e])
router.then(r => {
app.use(eui, {locale: zhCn})
app.use(r)
app.use(createPinia())
app.mount('#app')
})

35
web/src/utils/axios.js Normal file
View File

@@ -0,0 +1,35 @@
import axios from "axios";
import {ElMessage} from "element-plus";
import {getToken} from "./tools";
let ins = axios.create({
baseURL: process.env.NODE_ENV == "production" ? "/" : "/dev", timeout: 600000, withCredentials: true,
})
ins.interceptors.request.use(config => {
if (!config.withoutToken && !!getToken()) {
config.headers["Authorization"] = getToken()
}
if (config.params) {
Object.keys(config.params).map(e => {
if (typeof config.params[e] == "string") config.params[e] = config.params[e].trim()
})
}
return config
}, error => {
console.error(error)
})
ins.interceptors.response.use(res => {
if (res?.data?.code == 0) {
return res.data
} else if (res?.data?.code == 401) {
console.error("安全令牌验证无法通过!")
} else if (res?.data) {
return res.data
} else {
ElMessage.error("服务器异常,请联系管理员!")
console.error("服务器异常,请联系管理员!")
}
})
export default ins

41
web/src/utils/router.js Normal file
View File

@@ -0,0 +1,41 @@
import {createRouter, createWebHistory} from 'vue-router'
import {getToken} from './tools'
let home = {name: "工作台", path: "/v", component: () => import('../views/home')},
routes = [{name: "登录", path: "/login", component: () => import('../views/login')}]
const loadApps = () => {
const files = import.meta.glob('../apps/App*.vue'), apps = []
return Promise.all(Object.keys(files).map(path => {
return files?.[path]()?.then(file => {
if (file.default) {
let {name, label} = file.default, addApp = {
name,
label: label || name,
path: name,
component: () => import("../views/appEntry"),
app: file.default
}
return apps.push(addApp)
}
})
})).then(() => {
localStorage.setItem("apps", JSON.stringify(apps))
home.children = apps
routes.push(home)
})
}
export default loadApps().then(() => createRouter({
history: createWebHistory(), routes
})).then(router => {
//全局路由守卫
router.beforeEach((to, from, next) => {
console.log('%s=>%s', from.name, to.name)
if (getToken()) {
next({name: "工作台"})
} else if (to.name != '登录') {
next({name: "登录"})
} else next()
})
return router
})

7
web/src/utils/store.js Normal file
View File

@@ -0,0 +1,7 @@
import {defineStore} from "pinia/dist/pinia";
export const mainStore = defineStore('main', {
state: () => ({
user: {}
})
})

7
web/src/utils/tools.js Normal file
View File

@@ -0,0 +1,7 @@
export const getToken = () => localStorage.getItem("token")
export const $confirm = ()=>{
}
export default {
getToken,$confirm
}

View File

@@ -0,0 +1,46 @@
<template>
<component class="appEntry" v-if="app" :is="app" :instance="axios" :dict="$dict" :permissions="$permissions"/>
</template>
<script>
import {mapState} from "pinia/dist/pinia";
import {mainStore} from "../utils/store";
import {defineComponent} from "vue";
import Building from "../components/building";
export default {
name: "appEntry",
data() {
return {
apps: []
}
},
computed: {
...mapState(mainStore, ['user']),
app() {
if (this.name) return this.name
else {
if (this.$route.name == '工作台') return Console
let route = this.user.info?.menuSet.find(e => e.route == this.$route.name)
if (route?.app.indexOf('/') > -1) {//处理历史数据,后续慢慢调整
return () => import(`./${route.app}`)
} else if (defineComponent(route?.app)) {
return route.app
} else return Building
}
}
},
created() {
this.apps = JSON.parse(localStorage.getItem("apps"))
}
}
</script>
<style lang="scss" scoped>
.appEntry {
width: 100%;
height: calc(100vh - 180px);
}
</style>

140
web/src/views/home.vue Normal file
View File

@@ -0,0 +1,140 @@
<template>
<section class="home">
<div class="headerNav">
<b class="mar-r30" v-text="$sys.title"/>
<el-row class="fill">
<el-select placeholder="请选择系统" default-first-option v-model="system"/>
</el-row>
<el-row align="middle" class="right">
<i class="iconfont icon-toubutouxiang"/>
{{ user.name || "游客" }}欢迎您您已登录
<b class="pad-w10" v-text="user.areaName||'张新马府'"/>
地区
<b class="pad-w10" v-text="' | '"/>
<i class="iconfont icon-tuichu"/>
</el-row>
</div>
<el-row>
<slider-nav v-if="system"/>
<div class="mainContent fill">
<div v-if="$route.name=='工作台'" class="default fill">
<div>欢迎来到综合大数据平台中心 <span v-text="'请先选择系统'"/></div>
</div>
<template v-else>
<el-row class="breadcrumb" align="middle">
<div class="iconfont icon-dangqianweizhi" v-text="' 当前位置:'"/>
<el-breadcrumb separator="-">
<el-breadcrumb-item>基础数据</el-breadcrumb-item>
<el-breadcrumb-item>人口基础数据</el-breadcrumb-item>
</el-breadcrumb>
</el-row>
<el-scrollbar class="fill">
<router-view class="fill"/>
</el-scrollbar>
</template>
<div class="copyright color-a2v1c5" v-text="`copyright @2022 ${$sys.title}`"/>
</div>
</el-row>
</section>
</template>
<script>
import SliderNav from "../components/sliderNav";
import {mapState} from "pinia/dist/pinia";
import {mainStore} from "../utils/store";
export default {
name: "home",
components: {SliderNav},
computed: {
...mapState(mainStore, ['user'])
},
data() {
return {system: "1"}
},
created() {
console.log(this.$route)
}
}
</script>
<style lang="scss" scoped>
.home {
height: 100vh;
background: var(--background-color);
.headerNav {
width: 100vw;
height: 80px;
padding: 0 30px;
color: #fff;
background: var(--el-color-primary);
box-sizing: border-box;
align-items: center;
display: flex;
& > b {
font-size: 18px;
}
.right {
line-height: 24px;
}
.icon-tuichu {
font-size: 24px;
cursor: pointer;
&:hover {
opacity: .6;
}
}
.icon-toubutouxiang {
font-size: 16px;
margin-right: 10px;
}
}
& > .el-row {
height: calc(100vh - 80px);
}
.default {
background: url("../assets/consoleBg.png") no-repeat center;
padding-top: 170px;
text-align: center;
font-size: 20px;
span {
color: #E82301;
}
}
.breadcrumb {
line-height: 57px;
}
.copyright {
line-height: 42px;
text-align: center;
}
:deep(.mainContent) {
display: flex;
flex-direction: column;
padding-right: 20px;
.el-scrollbar {
background: #fff;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
.el-scrollbar__view {
min-height: 100%;
display: flex;
flex-direction: column;
}
}
}
}
</style>

34
web/src/views/login.vue Normal file
View File

@@ -0,0 +1,34 @@
<template>
<div class="login">
<ku-login :title="$sys.title" border/>
<div class="copyright color-a2v1c5" v-text="`copyright @2022 ${$sys.title}`"/>
</div>
</template>
<script>
import KuLogin from "../components/KuLogin/KuLogin";
export default {
name: "login",
components: {KuLogin},
}
</script>
<style lang="scss" scoped>
.login {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background: #F2F6FF;
.copyright {
margin-top: 30px;
font-size: 16px;
font-family: PingFang SC-Regular, PingFang SC;
}
}
</style>

10
web/vite.config.js Normal file
View File

@@ -0,0 +1,10 @@
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
extensions: ['.vue', '.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']
},
plugins: [vue()]
})