完成管理页面的结构

This commit is contained in:
LittleBoy 2022-12-06 15:29:26 +08:00
parent bd903154ab
commit 07230156e6
13 changed files with 222 additions and 68 deletions

View File

@ -1,19 +1,22 @@
<template> <template>
<div> <div>
<button @click="showMessage">显示消息</button>
<router-view/> <router-view/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
//
import message from "./components/message"; import message from "./components/message";
import {useUserStore} from "./service/store";
export default { export default {
name: "App", name: "App",
setup() { setup() {
useUserStore().updateInfo();
// TODO
return { return {
// login->index->
showMessage() { showMessage() {
message.toast('test display') message.toast('test display')
} }

View File

@ -1,5 +1,44 @@
:root { :root {
--primary-color: #fff; --primary-color: #1890ff;
--primary-color-hover: #40a9ff;
--primary-color-active: #096dd9;
--primary-color-outline: rgba(24, 144, 255, .2);
--primary-1: #e6f7ff;
--primary-2: #bae7ff;
--primary-3: #91d5ff;
--primary-4: #69c0ff;
--primary-5: #40a9ff;
--primary-6: #1890ff;
--primary-7: #096dd9;
--primary-color-deprecated-l-35: #cbe6ff;
--primary-color-deprecated-l-20: #7ec1ff;
--primary-color-deprecated-t-20: #46a6ff;
--primary-color-deprecated-t-50: #8cc8ff;
--primary-color-deprecated-f-12: rgba(24, 144, 255, .12);
--primary-color-active-deprecated-f-30: rgba(230, 247, 255, .3);
--primary-color-active-deprecated-d-02: #dcf4ff;
--success-color: #52c41a;
--success-color-hover: #73d13d;
--success-color-active: #389e0d;
--success-color-outline: rgba(82, 196, 26, .2);
--success-color-deprecated-bg: #f6ffed;
--success-color-deprecated-border: #b7eb8f;
--error-color: #ff4d4f;
--error-color-hover: #ff7875;
--error-color-active: #d9363e;
--error-color-outline: rgba(255, 77, 79, .2);
--error-color-deprecated-bg: #fff2f0;
--error-color-deprecated-border: #ffccc7;
--warning-color: #faad14;
--warning-color-hover: #ffc53d;
--warning-color-active: #d48806;
--warning-color-outline: rgba(250, 173, 20, .2);
--warning-color-deprecated-bg: #fffbe6;
--warning-color-deprecated-border: #ffe58f;
--info-color: #1890ff;
--info-color-deprecated-bg: #e6f7ff;
--info-color-deprecated-border: #91d5ff;
--primary-color-text: #333; --primary-color-text: #333;
--font-size: 14px; --font-size: 14px;
--font-size-small: calc(var(--font-size) - 2px); --font-size-small: calc(var(--font-size) - 2px);
@ -8,12 +47,16 @@
--border-radius: 1px; --border-radius: 1px;
--border-radius-middle: calc(var(--border-radius) + 2px); --border-radius-middle: calc(var(--border-radius) + 2px);
--border-radius-large: calc(var(--border-radius) + 3px); --border-radius-large: calc(var(--border-radius) + 3px);
--header-height: 80px;
--left-menu-width: 200px;
} }
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
*, *:before, *:after { *, *:before, *:after {
box-sizing: border-box; box-sizing: border-box;
} }
@ -21,10 +64,12 @@
input[type=text], input[type=password], input[type=number], textarea { input[type=text], input[type=password], input[type=number], textarea {
-webkit-appearance: none; -webkit-appearance: none;
} }
[class^=p-]::-ms-clear,[class*=p-]::-ms-clear,[class^=p-] input::-ms-clear,
[class*=p-] input::-ms-clear,[class^=p-] input::-ms-reveal,[class*=p-] input::-ms-reveal { [class^=p-]::-ms-clear, [class*=p-]::-ms-clear, [class^=p-] input::-ms-clear,
[class*=p-] input::-ms-clear, [class^=p-] input::-ms-reveal, [class*=p-] input::-ms-reveal {
display: none display: none
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 'Source Han Sans SC', 'Microsoft YaHei', 'Microsoft YaHei UI', "Helvetica Neue", sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 'Source Han Sans SC', 'Microsoft YaHei', 'Microsoft YaHei UI', "Helvetica Neue", sans-serif;
scroll-behavior: smooth; scroll-behavior: smooth;
@ -68,8 +113,9 @@ body {
pointer-events: all pointer-events: all
} }
.icon-svg{ .icon-svg {
} }
.pointer-cursor{
.pointer-cursor {
cursor: pointer; cursor: pointer;
} }

View File

@ -0,0 +1,3 @@
import PInput from './input.vue'
export default PInput

View File

@ -12,4 +12,5 @@ const app = createApp(App)
// 使用路由 // 使用路由
app.use(router) app.use(router)
app.use(createPinia()); app.use(createPinia());
// 将应用实例挂载到 模板中
app.mount('#vue-root-app') app.mount('#vue-root-app')

View File

@ -3,22 +3,29 @@ import Home from '../views/admin/Home.vue'
import Login from '../views/Login.vue' import Login from '../views/Login.vue'
import NotFound from '../views/NotFound.vue' import NotFound from '../views/NotFound.vue'
import Test from '../views/Test.vue' import Test from '../views/Test.vue'
import AdminLayout from '../views/layout/AdminLayout.vue'
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
path: '/', path: '/',
component: Test component: AdminLayout,
}, children: [
{ {
path: '/home', path: 'home',
component: Home component: Home
},
{
path: 'test',
component: Test
}
]
}, },
{ {
path: '/login', path: '/login',
component: Login component: Login
}, },
{ {
path:'/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
component: NotFound component: NotFound
} }
] ]

View File

@ -16,7 +16,8 @@ export const useTestStore = defineStore('test-store', {
}, },
actions: {} actions: {}
}) })
// 保存数据的key
const TOKEN_KEY = "user-login-token";
export const useUserStore = defineStore('user-store', () => { export const useUserStore = defineStore('user-store', () => {
const userinfo = ref<AdminLoginModel>() const userinfo = ref<AdminLoginModel>()
@ -25,11 +26,16 @@ export const useUserStore = defineStore('user-store', () => {
userinfo.value = data userinfo.value = data
} }
function token() {
if (userinfo.value && userinfo.value.token) return userinfo.value.token;
return localStorage.getItem(TOKEN_KEY)
}
async function login(params: any) { async function login(params: any) {
try { try {
const data = await http.post<AdminLoginModel>('/admin/user/login', params) const data = await http.post<AdminLoginModel>('/admin/user/login', params)
userinfo.value = data userinfo.value = data
localStorage.setItem("user-login-token", data.token) localStorage.setItem(TOKEN_KEY, data.token)
} catch (e) { } catch (e) {
message.toast('登录失败:' + e.message) message.toast('登录失败:' + e.message)
throw e; throw e;
@ -38,10 +44,10 @@ export const useUserStore = defineStore('user-store', () => {
async function logout() { async function logout() {
const data = await http.get<AdminLoginModel>('/admin/user/info') const data = await http.get<AdminLoginModel>('/admin/user/info')
localStorage.removeItem('user-login-token') localStorage.removeItem(TOKEN_KEY)
userinfo.value = null userinfo.value = null
} }
return {userinfo, login, logout} return {userinfo, login, logout, updateInfo, token}
}) })

View File

@ -1,4 +1,6 @@
import {toast} from "../components/message"; import {toast} from "../components/message";
import {useUserStore} from "../service/store";
import {useRoute, useRouter} from "vue-router";
export type HttpMethod = 'get' | 'post' | 'delete' | 'put' export type HttpMethod = 'get' | 'post' | 'delete' | 'put'
@ -69,12 +71,16 @@ class Http {
} }
} }
const headers: any = {
'Content-Type': contentType
}
const token = useUserStore().token();
if (token) headers.token = token
const r = useRoute(), router = useRouter();
fetch(httpConfig.baseURL + url, { fetch(httpConfig.baseURL + url, {
method, method,
body: data, body: data,
headers: { headers,
'Content-Type': contentType
}
}) })
.then(res => res.json()) // 只要json的响应数据 .then(res => res.json()) // 只要json的响应数据
.then((res: ResponseModel<T>) => { .then((res: ResponseModel<T>) => {
@ -84,6 +90,9 @@ class Http {
// 需要统一处理数据 // 需要统一处理数据
if (code === 403) { if (code === 403) {
toast("登录凭证无效或者已过期") toast("登录凭证无效或者已过期")
if (r.fullPath != '/login') {
router.replace('/login');
}
return; return;
} else if (err) { } else if (err) {
toast(httpConfig.globalErrorHandler[code]) toast(httpConfig.globalErrorHandler[code])

View File

@ -1,66 +1,69 @@
<template> <template>
<div class="login-wrapper"> <div class="login-wrapper">
<form @submit="onLogin"> <form @submit="onLogin">
<p> <p>
<input placeholder="请输入账号" type="text" v-model="data.account"> <PInput placeholder="请输入密码" type="text" v-model="data.account"/>
</p> </p>
<p> <p>
<input placeholder="请输入密码" type="text" v-model="data.password"> <PInput placeholder="请输入密码" type="password" v-model="data.password"/>
</p> </p>
<button :disabled="loading" :type="loading?'button':'submit'">{{ loading ? '正在登录' : '登录' }}</button> <button :disabled="loading" :type="loading?'button':'submit'">{{ loading ? '正在登录' : '登录' }}</button>
</form> </form>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import {reactive, ref} from "vue"; import {reactive, ref} from "vue";
import http from "../util/http"; import PInput from './../components/input'
import {useUserStore} from "../service/store"; import {useUserStore} from "../service/store";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
export default { export default {
name: "User", name: "User",
setup() { components: {PInput},
// vue () setup() {
// vue2 Object.definedProperty // vue ()
// vue3 Proxy // vue2 Object.definedProperty
const data = reactive({ // vue3 Proxy
account: '', const data = reactive({
password: '' account: '',
password: ''
})
const store = useUserStore()
const r = useRouter()
const loading = ref(false)
function onLogin(e: Event) {
e.preventDefault()
loading.value = true
store.login(data)
.then(() => {
r.replace('/home').then(() => console.log('success')).catch(e => console.log(e))
}) })
const store = useUserStore() .catch(e => {
const r = useRouter() console.log(e)
}).finally(() => {
const loading = ref(false) loading.value = false
})
function onLogin(e: Event) {
e.preventDefault()
loading.value = true
store.login(data)
.then(() => {
r.replace('/home').then(() => console.log('success')).catch(e => console.log(e))
})
.catch(e => {
console.log(e)
}).finally(() => {
loading.value = false
})
}
return {
data, onLogin, loading
}
} }
return {
data, onLogin, loading
}
}
} }
</script> </script>
<style scoped> <style scoped>
.error { .error {
color: red; color: red;
}
p{
margin: 10px 0;
} }
.login-wrapper { .login-wrapper {
width: 500px; width: 500px;
margin: 50px auto; margin: 50px auto;
} }
</style> </style>

View File

@ -20,7 +20,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import PInput from "../components/input/index.vue"; import PInput from "../components/input";
import {reactive} from "vue"; import {reactive} from "vue";
const data = reactive({ const data = reactive({

View File

@ -1,5 +1,9 @@
<template> <template>
<h1>Home</h1> <h1> Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home</h1>
<h1> Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home</h1>
<h1> Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home</h1>
<h1> Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home</h1>
<h1> Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home Home</h1>
<router-link to="/user">User</router-link> <router-link to="/user">User</router-link>
</template> </template>

View File

@ -0,0 +1,71 @@
<template>
<div class="app-admin-layout">
<div class="header">
<!-- 头部 -->
<div class="user-info">
<span>
{{ userStore.userinfo?.account }}
</span>
</div>
</div>
<div class="main">
<div class="left-menu">左侧的菜单
<div>
<router-link to="/test">TEST</router-link>
</div>
<div>
<router-link to="/home">HOME</router-link>
</div>
</div>
<div class="content-wrapper">
<!-- 内容区域-->
<router-view/>
</div>
</div>
</div>
</template>
<script setup>
import {useUserStore} from "../../service/store";
const userStore = useUserStore();
</script>
<style lang="less">
.app-admin-layout {
min-height: 100vh;
}
.header {
background-color: var(--primary-color);
color: white;
line-height: 80px;
padding: 0 20px;
text-align: right;
position: fixed;
height: 80px;
left: 0;
right: 0;
top: 0;
}
.main {
background-color: #eee;
padding-top: var(--header-height);
padding-left: var(--left-menu-width);
.left-menu {
width: 200px;
background-color: black;
color: white;
padding: 20px;
position: fixed;
left: 0;
top: 0;
bottom: 0;
}
.content-wrapper {
}
}
</style>

View File

@ -2,6 +2,7 @@ import vue from '@vitejs/plugin-vue'
import vueJsxPlugin from "@vitejs/plugin-vue-jsx"; import vueJsxPlugin from "@vitejs/plugin-vue-jsx";
export default { export default {
// 开发服务信息
server: { server: {
host:'::' host:'::'
}, },