This commit is contained in:
mi9688 2024-11-28 12:57:22 +08:00
parent 3db326afa9
commit eaa2a85c80
38 changed files with 5616 additions and 0 deletions

11
.eslintrc.cjs Normal file
View File

@ -0,0 +1,11 @@
/* eslint-env node */
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

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

@ -0,0 +1,6 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint"
]
}

21
index.html Normal file
View File

@ -0,0 +1,21 @@
<!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>demo</title>
<style>
</style>
</head>
<body>
<div id="app">
<!-- 这里是你的单页面应用内容 -->
</div>
<script type="module" src="/src/main.js"></script>
<script>
</script>
</body>
</html>

12
jsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
}
}

3554
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "fat-vue3",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
},
"dependencies": {
"@microsoft/fetch-event-source": "^2.0.1",
"axios": "^1.6.8",
"crypto-js": "^4.2.0",
"echarts-stat": "^1.2.0",
"element-plus": "^2.6.3",
"md-editor-v3": "^4.17.4",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"vue3-scroll-seamless": "^1.0.6",
"vue3-seamless-scroll": "^2.0.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"echarts": "^5.5.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"sass": "^1.75.0",
"vite": "^5.2.8"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

41
src/App.vue Normal file
View File

@ -0,0 +1,41 @@
<script setup>
import router from '@/router/index'
import {ref,onMounted} from 'vue'
const isLogging=()=>{
const user=localStorage.getItem('userInfo')
if(user){
console.log('登录中')
if(!JSON.parse(user).token){
return false
}
return true
}
return false
}
onMounted(()=>{
console.log(isLogging())
if(isLogging()){
router.replace({ path: "/index" })
}else{
router.replace({ path: "/login" })
}
})
</script>
<template>
<div> <RouterView /></div>
</template>
<style scoped>
</style>

29
src/apis/loginApi.ts Normal file
View File

@ -0,0 +1,29 @@
import http from '@/utils/http'
//用户登录
export function loginAPI({ account, password}) {
return http({
url: 'admin/login',
method: 'POST',
data: {
username: account,
password: password
}
})
}
//账密注册
export function registerAPI({ account, password,password2}) {
return http({
url: 'user/register',
method: 'POST',
data: {
account: account,
password: password,
password2:password2
}
})
}

86
src/assets/base.css Normal file
View File

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

BIN
src/assets/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

1
src/assets/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

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

@ -0,0 +1,35 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}

BIN
src/assets/speech.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
src/assets/think.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
src/assets/头像.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

35
src/main.js Normal file
View File

@ -0,0 +1,35 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from '@/router/index'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// **main.js**
import {vue3ScrollSeamless} from "vue3-scroll-seamless";
// 引入初始化样式文件
import '@/styles/common.scss'
// 数据可视化
import * as echarts from 'echarts'
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)
app.use(echarts)
app.component('vue3ScrollSeamless',vue3ScrollSeamless)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus, {
locale: zhCn,
})
app.mount('#app')

31
src/router/index.js Normal file
View File

@ -0,0 +1,31 @@
import { createRouter, createWebHistory } from 'vue-router'
// import Container from '@/views/container.vue'
import Layout from '@/views/layout/Layout.vue'
import Index from '@/views/pages/index/Index.vue'
import Line from '@/views/pages/line/Line.vue'
import Login from '@/views/pages/login/Login.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'container',
component: Layout,
children:[
{path:'/',redirect:"/index"},
{path:'/index',component:Index},
{path:'/line',component:Line},
]
},
{
path: '/login',
name: 'login',
component: Login
}
]
})
export default router

73
src/stores/user.js Normal file
View File

@ -0,0 +1,73 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import router from '@/router/index'
import { loginAPI,registerAPI} from '@/apis/loginApi'
import { ElMessage } from 'element-plus'
export const useUserStore = defineStore('user', () => {
// 定义管理用户数据的state
const userInfo = ref({
name:'',
token:''
})
// 从本地存储中加载数据
const loadUserInfoFromLocalStorage = () => {
const savedUserInfo = JSON.parse(localStorage.getItem('userInfo'))
if (savedUserInfo) {
console.log('用户信息本地存在!')
userInfo.value = savedUserInfo
return
}
// localStorage.removeItem('userInfo')
// sessionStorage.clear()
// if(router.currentRoute.value.path==='/login'){
// return
// }
// ElMessage({ type: 'error', message: '登录状态丢失,请重新登录!' })
// router.replace('/login')
}
// 在 store 创建时加载数据
loadUserInfoFromLocalStorage()
//账密登录
const getUserInfo = async ({ account, password }) => {
console.log('本地没有用户信息,开始请求接口')
const res = await loginAPI({ account: account, password: password })
if(res.code===0){
userInfo.value = res.data
// 保存数据到本地存储
localStorage.setItem('userInfo', JSON.stringify(res.data))
}
return res
}
// 注册并登录
const registerAndLogin = async ({ account, password, password2 }) => {
const res = await registerAPI({ account: account, password: password, password2: password2 })
if(res.code===0){
userInfo.value = res.data
// 保存数据到本地存储
localStorage.setItem('userInfo', JSON.stringify(res.data))
}
return res
}
// 清除用户信息
const clearUserInfo = () => {
userInfo.value = {}
localStorage.removeItem('userInfo')
}
// 返回state和actions
return {
userInfo,
getUserInfo,
clearUserInfo,
registerAndLogin
}
})

86
src/styles/common.scss Normal file
View File

@ -0,0 +1,86 @@
// 重置样式
* {
box-sizing: border-box;
}
html {
height: 100vh;
width: 100vw;
font-size: 14px;
}
::-webkit-scrollbar {
width: 0px; /* 设置滚动条的宽度 */
height: 0px;
}
::-webkit-scrollbar-track {
background-color: #ffffff; /* 设置滚动条轨道的背景颜色 */
}
::-webkit-scrollbar-thumb {
background-color: #00144d; /* 设置滚动条滑块的背景颜色 */
}
body {
// transform: scale(0.8);
height: 100%;
width: 100%;
// color: #ffffff;
background-color: #ffffff;
/* 隐藏水平滚动条 */
overflow-x:auto;
min-width: 100vw;
font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI',
'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei',
sans-serif;
}
body,
ul,
h1,
h3,
h4,
p,
dl,
dd {
padding: 0;
margin: 0;
}
a {
text-decoration: none;
color: #333;
outline: none;
}
i {
font-style: normal;
}
img {
max-width: 100%;
max-height: 100%;
vertical-align: middle;
// background: #ebebeb url('@/assets/images/200.png') no-repeat center / contain;
//当图片未获取到的默认图片
}
ul {
list-style: none;
}
#app {
width: 100%;
height: 100%;
// display: flex;
// min-height: 100%;
// user-select: none;//禁止复制
// background: #f7b8b8;
}
.common-layout{
width: 100%;
height: 100%;
}
.el-link:hover {
color: #ff6600;
}
.el-link:hover .el-icon {
color: #ff6600;
}

65
src/utils/http.js Normal file
View File

@ -0,0 +1,65 @@
//axios基础的封装
import axios from "axios"
import { ElMessage } from "element-plus"
import "element-plus/theme-chalk/el-message.css"
import { useUserStore } from "@/stores/user"
// import router from "@/router/index"
//axios基础封装1默认
const httpInstance1 = axios.create({
//配置基地址
baseURL: "http://192.168.1.6:8080/",
// baseURL: "http://www.mi9688.top/api/",
//配置超时时间
timeout: 20000,
})
// axios请求拦截器
httpInstance1.interceptors.request.use(
(config) => {
const userStore = useUserStore();
const token = userStore.userInfo.token;
// console.log("请求拦截器得到token" + token);
config.headers["Content-Type"] = "application/json";
if (token) {
config.headers.Authorization = "Bearer " + token;
}
eval()
return config;
},
(e) => Promise.reject(e)
)
// axios响应式拦截器
httpInstance1.interceptors.response.use(
(res) => res.data,
(e) => {
console.log("响应拦截器:")
console.log(e.response)
//统一错误提示
//服务器错误处理
if (e.response.status === 500) {
ElMessage({
type: "error",
message: "服务器错误!",
})
}
//业务异常处理
if(e.response.data.code===0){
ElMessage({
type: "error",
message: e.response.data.message,
})
}
ElMessage({
type: "error",
message: "网络波动异常!",
})
return Promise.reject(e)
}
);
export default httpInstance1

90
src/views/container.vue Normal file
View File

@ -0,0 +1,90 @@
<template>
<div class="common-layout">
</div>
</template>
<script setup>
import { ElContainer, ElHeader, ElMain } from 'element-plus'
import LayoutHeader from '@/views/layout/LayoutHeader.vue'
import User from '@/views/layout/User.vue'
import { onMounted } from 'vue'
onMounted(() => {
})
</script>
<style scoped>
.container {
height: 100%;
width: 100%;
/* background-color: bisque; */
}
.header {
background-color: rgb(0, 0, 0, 0.3);
/* box-shadow: 0px 0px 1px 1px rgb(255, 255, 255); */
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
z-index: 1;
width: 100%;
/* height: 100%; */
padding: 0px;
margin: 0px;
}
.header-container {
width: 100%;
/* height: 10%; */
display: inline-flex;
background-color: antiquewhite;
background-image: url('../assets/layout-bg.png');
background-size: 100% 100%;
background-repeat: no-repeat;
}
.header-title {
width: 300px;
height: 40px;
/* background-color: aqua; */
font-size: 25px;
color: rgb(255, 255, 255);
font-family: Arial, sans-serif;
position: relative;
right: 405px;
top: 10px;
}
.user {
position: absolute;
right: 20px;
top: 10px
}
.main {
background-color: rgb(255, 255, 255);
}
.canvas {
z-index: 0;
position: fixed;
width: 100vw;
height: 100vh;
}
.zoom-content {
position: absolute;
min-width: 100vw;
min-height: 100vh;
background-color: rgb(255, 255, 255, 0.01);
}
.all-content {
background-color: rgb(161, 202, 202, 0.1);
}
</style>

View File

@ -0,0 +1,47 @@
<template>
<div class="card-container">
<div class="title-container">肥胖者年龄与性别分布</div>
<div class="chart" id="ping"></div>
</div>
</template>
<script setup>
import {ref ,onMounted} from 'vue'
import * as echarts from 'echarts';
const init=()=>{
}
onMounted(()=>{
init()
})
</script>
<style lang="scss" scoped>
.card-container{
width: 400px;
height: 350px;
}
.title-container{
color: rgb(255, 255, 255);
font-size: 18px;
width: 100%;
display: flex;
justify-content: center;
position: relative;
top:10px;
}
.chart{
width: 100%;
height: 300px;
display: flex;
justify-content: center;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<div>
<el-container class="layout-container-demo" style="height: 100vh">
<el-aside>
<div class="aside" id="left-aside">
<the-aside></the-aside>
</div>
</el-aside>
<el-container>
<el-header>
<the-header></the-header>
</el-header>
<el-main>
<el-scrollbar>
<router-view></router-view>
</el-scrollbar>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script lang="ts" setup>
import {ref,onMounted} from 'vue'
import TheAside from '@/views/layout/TheAside.vue'
import TheHeader from '@/views/layout/TheHeader.vue'
onMounted(()=>{
})
</script>
<style scoped lang="scss">
.layout-container-demo .el-header {
position: relative;
background-color: #ffffff;
color: #ffffff;
transition: background-color 1s ease;
height: 45px;
}
.layout-container-demo .el-aside {
color: #ffffff;
background-color: #5183e0;
// border-right: 2px solid #007bff;
transition: background-color 1s ease;
width: auto;
}
.layout-container-demo .el-main {
padding: 0;
background-color: #ececec;
transition: background-color 1s ease;
// margin: 5px;
}
</style>

View File

@ -0,0 +1,99 @@
<script lang="ts" setup>
import { Search } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
import { ref } from 'vue';
import router from '../../router/index'
//
const input2 = ref('');
//
const f1List=ref([
{id:1,name:'首页',path:'/index',icon:'House',color:'white'},
{id:3,name:'在线预测',path:'/predict',icon:'Aim',color:'white'},
{id:2,name:'指标分析',path:'/metrics',icon:'Aim',color:'white'},
{id:5,name:'智能问答',path:'/qanda',icon:'Aim',color:'white'},
{id:6,name:'菜品识别',path:'/dishRecords',icon:'Aim',color:'white'},
{id:4,name:'我的记录',path:'/records',icon:'Aim',color:'white'},
])
const selecedFunction = ref(0)
const seleced = (index) => {
//
sessionStorage.removeItem('selectedFunction1')
//
sessionStorage.setItem('selectedFunction', index)
selecedFunction.value = index
}
//
const selectedVal = sessionStorage.getItem('selectedFunction')
if (selectedVal !== null) {
selecedFunction.value = parseInt(selectedVal)
} else {
if (userStore.userInfo.token) {
router.replace('/index')
}
}
</script>
<template>
<div class='content-item'>
<div class="flist">
<ul class="ul">
<li class="f1" v-for="(item, index) in f1List" :key="item.id" :class="{ 'seleced': index === selecedFunction }">
<RouterLink :to="item.path" @click="seleced(index)">
<div >
<div class="text">{{ item.name }}</div>
</div>
</RouterLink>
</li>
</ul>
</div>
</div>
</template>
<style scoped lang='scss'>
.content-item {
width:1100px;
display: flex;
align-items: center;
height: 55px;
}
.flist{
position: relative;
}
.ul {
width: 100%;
display: flex;
li{
margin: 10px;
display: block;
align-items: center;
}
}
.text{
color: rgb(17, 193, 232);
font-size: 15px
}
.seleced {
color: chartreuse;
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<div class="container">
<div>
<TheLogo></TheLogo>
</div>
<el-scrollbar max-height="1000px">
<div class="the-menu">
<TheMenu></TheMenu>
</div>
</el-scrollbar>
</div>
</template>
<script setup>
import TheMenu from '@/views/layout/TheMenu.vue';
import TheLogo from '@/views/layout/TheLogo.vue';
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,89 @@
<template>
<div class="toolbar">
<div style="position: absolute;right: 20px;"><User /></div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import User from './User.vue';
onMounted(() => {
});
onUnmounted(() => {
});
</script>
<style scoped lang="scss">
.re {
color: var(--txt-color);
}
.toolbar {
display: inline-flex;
gap: 20px;
align-items: center;
height: 100%;
right: 20px;
width: 100%;
}
.theme {
position: absolute;
right: 110px;
.theme-img {
display: flex;
align-items: center;
justify-content: center;
background-color: var(--prominent-color);
border-radius: 10px;
width: 25px;
height: 25px;
}
}
.user {
display: inline-flex;
position: absolute;
align-items: center;
justify-content: end;
right: 0%;
width: 120px;
height: 40px;
/* background-color: rgb(236, 144, 24); */
}
.avatar-img {
border-radius: 10px;
}
.name {
margin-left: 3px;
width: 4.3em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.nologin {
position: absolute;
display: flex;
align-items: center;
/* background-color: aqua; */
right: 0%;
width: 80px;
height: 40px;
font-size: 16px;
font-weight: 600;
font-family: Arial, Helvetica, sans-serif;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<div class="container">
<div class="logo-wrapper vertical-text" id="logo-wrapper">
<div class="logp-image-container"><img class="logo" src="../../assets/logo.svg" alt=""> </div>
</div>
</div>
<div class="divider"></div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss" >
.container {
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 1s ease;
}
.logo-wrapper {
width: 80%;
height: 80%;
}
.vertical-text {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: var(--logo-txt-size);
color: transparent;
}
.logp-image-container{
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.logo{
width: 30px;
height: 30px;
background-image: url('../../assets/logo.svg');
background-size: 100% 100%;
}
.divider{
margin: auto;
margin-top: 20px;
width: 60%;
border-bottom: 0.5px solid var(--txt-color);
opacity: 0.2;
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<div class="navigation">
<ul>
<li class="list">
<el-tooltip placement="right" content="item1">
<RouterLink :to="'index'">
<a href="#">
<span class="icon"><el-icon size="18">
<House />
</el-icon></span>
</a>
</RouterLink>
</el-tooltip>
</li>
<li class="list ">
<el-tooltip placement="right" content="item2">
<RouterLink :to="'line'">
<a href="#">
<span class="icon"><el-icon size="18">
<EditPen />
</el-icon></span>
</a>
</RouterLink>
</el-tooltip>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue'
onMounted(() => {
const list = document.querySelectorAll('.list');
function activelink() {
list.forEach((item) =>
item.classList.remove('active'));
this.classList.add('active');
// localStorage
localStorage.setItem('selectedMenuIndex', Array.from(list).indexOf(this));
}
list.forEach((item) =>
item.addEventListener('click', activelink));
// localStorage
const selectedMenuIndex = localStorage.getItem('selectedMenuIndex');
if (selectedMenuIndex) {
list[selectedMenuIndex].classList.add('active');
}
})
</script>
<style scoped lang="scss">
.navigation {
position: relative;
margin: auto;
min-height: 500px;
width: 80%;
display: flex;
justify-content: center;
box-sizing: initial;
background-color: var(--bg2);
transition: background-color 1s ease;
overflow-x: hidden;
}
.navigation ul {
position: absolute;
top: 0;
left: 0;
width: 100%;
padding-top: 40px;
}
.navigation ul li {
margin-bottom: 10px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
list-style: none;
width: 100%;
border-radius: 18px;
}
.navigation ul li.active {
background: var(--active-color);
}
.navigation ul li a {
position: relative;
display: block;
width: 100%;
display: flex;
text-decoration: none;
color: var(--txt-color);
}
.navigation ul li.active a {
color: var(--prominent-txt-color);
}
.navigation ul li a .icon {
display: flex;
justify-content: center;
align-items: center;
height: 50px;
text-align: center;
margin: auto;
}
.navigation ul li a .icon ion-icon {
position: relative;
font-size: 1.5em;
z-index: 1;
}
.navigation ul li a .left-menu-item-title {
position: relative;
right: 25px;
display: block;
padding-left: 10px;
height: 60px;
line-height: 60px;
white-space: nowrap;
}
</style>

64
src/views/layout/User.vue Normal file
View File

@ -0,0 +1,64 @@
<script setup lang="ts">
import { ref} from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
//退
const exit = () => {
localStorage.removeItem('userInfo')
sessionStorage.clear()
router.push('/login')
}
</script>
<template>
<div v-if="userStore.userInfo.token" class="avatar">
<el-dropdown>
<img class="avatar-img" src="@/assets/头像.png" width="30px" alt="">
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="exit">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="" v-else>
<div class="line">|</div>
<RouterLink to="/login">
<div class="login">登录</div>
</RouterLink>
</div>
</template>
<style>
.avatar {
width: 35px;
height: 35px;
border-radius: 50%;
}
.login {
color: rgb(0, 0, 0);
font-size: 16px;
/* position: relative;
top: 10px; */
display: inline-flex;
}
.line {
color: white;
position: relative;
/* top: 10px; */
right: 10px;
display: inline-flex;
}
</style>

View File

@ -0,0 +1,181 @@
<template>
<div>
<el-dialog v-model="dialogVisible" width="500" draggable modal :before-close="() => { goBack() }">
<div class="title-container">
<div class="title">基本信息</div>
</div>
<div class="user-info-container">
<div class="center">
<el-form ref="ruleFormRef" :model="userForm" :rules="rules">
<el-form-item label="" prop="username">
<span slot="label">
<span>用户昵称</span>
<span style="color: red">*</span>
</span>
<el-input v-model="userForm.username" placeholder="请填写你的用户名称"></el-input>
</el-form-item>
<el-form-item label="" prop="account">
<span slot="label">
<span>账号</span>
<span style="color: red">*</span>
</span>
<el-input v-model="userForm.account" placeholder="请填写你的账号" disabled></el-input>
</el-form-item>
<el-form-item label="" prop="password">
<span slot="label">
<span>密码</span>
<span style="color: red">*</span>
<el-input v-model="userForm.password" placeholder="请填写你的密码" type="password" show-password></el-input>
</span>
</el-form-item>
</el-form>
</div>
</div>
<div class="proposal-container">
</div>
<template #footer>
<div class="dialog-footer">
<el-button icon="Close" @click="goBack()">取消</el-button>
<el-button type="primary" icon="SuccessFilled" color="gold" @click="saveEdit(ruleFormRef)">
保存
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, defineProps } from 'vue'
import { ElDialog, ElForm, ElFormItem,ElMessage } from 'element-plus'
import { getUserInfoAPI, updateUserInfoAPI } from '@/apis/userApi'
import type { ComponentSize, FormInstance, FormRules } from 'element-plus'
import { el } from 'element-plus/es/locale';
interface userForm {
username: string,
account: string,
password: string,
}
const userForm = reactive<userForm>({
username: '',
account: '',
password: ''
})
const rules = reactive<FormRules<userForm>>({
username: [
{
required: true,
message: '用户名不能为空',
trigger: 'blur',
},
{
type: 'string',
min: 1,
max: 20,
message: '用户名长度超过限制',
trigger: 'blur',
}
],
password:[
{
required:true,
message:'密码不能为空',
trigger: 'blur',
},
{
type: 'string',
min: 6,
max: 16,
message: '密码长度必须在6~16位之间',
trigger: 'blur',
}
]
})
const ruleFormRef = ref<FormInstance>()
//
const emit = defineEmits(["exitEditPage", "flushListPage"])
function goBack() {
emit("exitEditPage");
}
const dialogVisible = ref(true)
//
const getUserInfo = async () => {
const res = await getUserInfoAPI()
if (res.code == 0) {
userForm.username = res.data.user_name
userForm.account = res.data.user_account
userForm.password = res.data.password
}
}
//
const saveEdit = (formEl: FormInstance | undefined) => {
console.log('submit!')
if (!formEl) return
formEl.validate(async (valid, fields) => {
if (valid) {
const res=await updateUserInfoAPI(userForm)
if(res.code===0){
ElMessage({type:'success',message:'修改成功!'})
}
} else {
console.log('error submit!', fields)
ElMessage({ type: 'warning', message: '请完整填写表单!' })
}
})
}
onMounted(() => {
getUserInfo()
})
</script>
<style lang="scss" scoped>
.title-container {
display: flex;
justify-content: center;
position: relative;
top: -20px;
}
.title {
font-size: 16px;
}
.center {
display: flex;
justify-content: center;
// height: 32px;
// background-color: aqua;
align-items: center;
}
</style>

View File

@ -0,0 +1,19 @@
<template>
<div >
<h1>index1111111111111111111111111111111111</h1>
</div>
</template>
<script setup>
import {ref ,onMounted} from 'vue'
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,19 @@
<template>
<div >
<h1>line</h1>
</div>
</template>
<script setup>
import {ref ,onMounted} from 'vue'
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,471 @@
<template>
<div class="login-root">
<div class="box-root flex-flex flex-direction--column" style="min-height: 100vh;flex-grow: 1;">
<div class="loginbackground box-background--white padding-top--64">
<div class="loginbackground-gridContainer">
<div class="box-root flex-flex" style="grid-area: top / start / 8 / end;">
<div class="box-root"
style="background-image: linear-gradient(white 0%, rgb(247, 250, 252) 33%); flex-grow: 1;">
</div>
</div>
<div class="box-root flex-flex" style="grid-area: 4 / 2 / auto / 5;">
<div class="box-root box-divider--light-all-2 animationLeftRight tans3s" style="flex-grow: 1;">
</div>
</div>
<div class="box-root flex-flex" style="grid-area: 6 / start / auto / 2;">
<div class="box-root box-background--blue800" style="flex-grow: 1;"></div>
</div>
<div class="box-root flex-flex" style="grid-area: 7 / start / auto / 4;">
<div class="box-root box-background--blue animationLeftRight" style="flex-grow: 1;"></div>
</div>
<div class="box-root flex-flex" style="grid-area: 8 / 4 / auto / 6;">
<div class="box-root box-background--gray100 animationLeftRight tans3s" style="flex-grow: 1;">
</div>
</div>
<div class="box-root flex-flex" style="grid-area: 2 / 15 / auto / end;">
<div class="box-root box-background--cyan200 animationRightLeft tans4s" style="flex-grow: 1;">
</div>
</div>
<div class="box-root flex-flex" style="grid-area: 3 / 14 / auto / end;">
<div class="box-root box-background--blue animationRightLeft" style="flex-grow: 1;"></div>
</div>
<div class="box-root flex-flex" style="grid-area: 4 / 17 / auto / 20;">
<div class="box-root box-background--gray100 animationRightLeft tans4s" style="flex-grow: 1;">
</div>
</div>
<div class="box-root flex-flex" style="grid-area: 5 / 14 / auto / 17;">
<div class="box-root box-divider--light-all-2 animationRightLeft tans3s" style="flex-grow: 1;">
</div>
</div>
</div>
</div>
<div class="box-root padding-top--24 flex-flex flex-direction--column" style="flex-grow: 1; z-index: 9;">
<div class="box-root padding-top--48 padding-bottom--24 flex-flex flex-justifyContent--center">
<h1 v-if="page === 'login'"><a href="http://blog.stackfindover.com/" rel="dofollow">管理员登录</a></h1>
</div>
<div class="formbg-outer">
<div class="formbg">
<div class="formbg-inner padding-horizontal--48">
<form id="stripe-login" v-show="page === 'login'">
<div class="field padding-bottom--24">
<label for="account">账号</label>
<input id="account" name="account" pattern=".{5,18}" title="账号长度必须为5到18位" placeholder="输入账号"
required>
</div>
<div class="field padding-bottom--24">
<div class="grid--50-50">
<label for="password">密码</label>
<div class="reset-pass">
<!-- <a href="#">忘记密码</a> -->
</div>
</div>
<input id="password" type="password" name="password" pattern=".{6,16}"
title="密码长度必须为6到16位" placeholder="输入密码" required>
</div>
<div class="field field-checkbox padding-bottom--24 flex-flex align-center">
<label for="checkbox">
<input id="savePassword" type="checkbox"> 记住密码
</label>
</div>
<div class="field padding-bottom--24">
<input type="submit" name="submit" value="登录">
</div>
<div class="field">
</div>
</form>
</div>
</div>
<div class="footer-link padding-top--24">
<div class="listing padding-top--24 padding-bottom--24 flex-flex center-center">
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { registerAPI, loginAPI } from '@/apis/loginApi'
import { useUserStore } from "@/stores/user";
import { ElMain, ElMessage ,ElNotification} from 'element-plus';
import router from '@/router/index'
import CryptoJS from "crypto-js";
const userStore = useUserStore();
const page = ref('login')
//
const savaPassword = () => {
var rememberPasswordCheckbox = document.getElementById("savePassword");
if (rememberPasswordCheckbox.checked) {
console.log("记住密码已勾选:"+document.getElementById("account").value);
localStorage.setItem('account', document.getElementById("account").value)
localStorage.setItem('password', CryptoJS.AES.encrypt(document.getElementById("password").value, 'mijiu').toString())
} else {
console.log("记住密码未勾选");
}
}
//
const loadcp = () => {
var account = localStorage.getItem("account") ? localStorage.getItem("account") : null;
var password = localStorage.getItem("password") ? CryptoJS.AES.decrypt(localStorage.getItem("password"), 'mijiu').toString(CryptoJS.enc.Utf8) : null;
document.getElementById("account").value = account
document.getElementById("password").value = password
document.getElementById("savePassword").checked = true
};
//
const login = async () => {
document.getElementById("stripe-login").addEventListener("submit", async function (event) {
event.preventDefault();
var account = document.getElementById("account").value;
var password = document.getElementById("password").value;
const res = await userStore.getUserInfo({ account, password })
if (res.code === 0) {
// ElMessage({ type: 'success', message: '' })
ElNotification({
title: '登录成功!',
message: '欢迎您登录系统!',
type: 'success',
})
savaPassword()
document.getElementById("account").value=''
document.getElementById("password").value=''
router.replace({ path: "/index" })
}
else{
ElMessage({ type: 'error', message: res.message })
}
});
}
//
// const register = async () => {
// document.getElementById("stripe-register").addEventListener("submit", async function (event) {
// event.preventDefault();
// var account = document.getElementById("account_r").value;
// var password = document.getElementById("password_r").value;
// var password2 = document.getElementById("password2").value;
// //
// if (password !== password2) {
// ElMessage({ type: 'error', message: '' })
// return
// }
// const res = await userStore.registerAndLogin({ account: account, password: password, password2: password2 })
// if (res.code === 0) {
// ElNotification({
// title: '',
// message: '',
// type: 'success',
// })
// savaPassword()
// document.getElementById("account_r").value=''
// document.getElementById("password_r").value=''
// document.getElementById("password2").value=''
// router.replace({ path: "/index" })
// }else{
// ElMessage({ type: 'error', message: res.message })
// }
// })
// }
onMounted(() => {
login()
// register()
loadcp()
})
</script>
<style lang="scss" scoped>
* {
padding: 0;
margin: 0;
color: #1a1f36;
box-sizing: border-box;
word-wrap: break-word;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Ubuntu, sans-serif;
}
h1 {
letter-spacing: -1px;
}
a {
color: #5469d4;
text-decoration: unset;
}
.login-root {
background-color: #212d63;
display: flex;
width: 100%;
min-height: 100vh;
overflow: hidden;
}
.loginbackground {
min-height: 692px;
position: fixed;
bottom: 0;
left: 0;
right: 0;
top: 0;
z-index: 0;
overflow: hidden;
}
.flex-flex {
display: flex;
}
.align-center {
align-items: center;
}
.center-center {
align-items: center;
justify-content: center;
}
.box-root {
box-sizing: border-box;
}
.flex-direction--column {
-ms-flex-direction: column;
flex-direction: column;
}
.loginbackground-gridContainer {
display: -ms-grid;
display: grid;
-ms-grid-columns: [start] 1fr [left-gutter] (86.6px)[16] [left-gutter] 1fr [end];
grid-template-columns: [start] 1fr [left-gutter] repeat(16, 86.6px) [left-gutter] 1fr [end];
-ms-grid-rows: [top] 1fr [top-gutter] (64px)[8] [bottom-gutter] 1fr [bottom];
grid-template-rows: [top] 1fr [top-gutter] repeat(8, 64px) [bottom-gutter] 1fr [bottom];
justify-content: center;
margin: 0 -2%;
transform: rotate(-12deg) skew(-12deg);
}
.box-divider--light-all-2 {
box-shadow: inset 0 0 0 2px #e3e8ee;
}
.box-background--blue {
background-color: #5469d4;
}
.box-background--white {
background-color: #ffffff;
}
.box-background--blue800 {
background-color: #212d63;
}
.box-background--gray100 {
background-color: #e3e8ee;
}
.box-background--cyan200 {
background-color: #7fd3ed;
}
.padding-top--64 {
padding-top: 64px;
}
.padding-top--24 {
padding-top: 24px;
}
.padding-top--48 {
padding-top: 48px;
}
.padding-bottom--24 {
padding-bottom: 24px;
}
.padding-horizontal--48 {
padding: 48px;
}
.padding-bottom--15 {
padding-bottom: 15px;
}
.flex-justifyContent--center {
-ms-flex-pack: center;
justify-content: center;
}
.formbg {
margin: 0px auto;
width: 100%;
max-width: 448px;
background: white;
border-radius: 4px;
box-shadow: rgba(60, 66, 87, 0.12) 0px 7px 14px 0px, rgba(0, 0, 0, 0.12) 0px 3px 6px 0px;
}
span {
display: block;
font-size: 20px;
line-height: 28px;
color: #1a1f36;
}
label {
margin-bottom: 10px;
}
.reset-pass a,
label {
font-size: 14px;
font-weight: 600;
display: block;
}
.reset-pass>a {
text-align: right;
margin-bottom: 10px;
}
.grid--50-50 {
display: grid;
grid-template-columns: 50% 50%;
align-items: center;
}
.field input {
font-size: 16px;
line-height: 28px;
padding: 8px 16px;
width: 100%;
min-height: 44px;
border: unset;
border-radius: 4px;
outline-color: rgb(84 105 212 / 0.5);
background-color: rgb(255, 255, 255);
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.16) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px;
}
input[type="submit"] {
background-color: rgb(84, 105, 212);
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.12) 0px 1px 1px 0px,
rgb(84, 105, 212) 0px 0px 0px 1px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(60, 66, 87, 0.08) 0px 2px 5px 0px;
color: #fff;
font-weight: 600;
cursor: pointer;
}
.field-checkbox input {
width: 20px;
height: 15px;
margin-right: 5px;
box-shadow: unset;
min-height: unset;
}
.field-checkbox label {
display: flex;
align-items: center;
margin: 0;
}
a.ssolink {
display: block;
text-align: center;
font-weight: 600;
}
.footer-link span {
font-size: 14px;
text-align: center;
}
.listing a {
color: #697386;
font-weight: 600;
margin: 0 10px;
}
.animationRightLeft {
animation: animationRightLeft 2s ease-in-out infinite;
}
.animationLeftRight {
animation: animationLeftRight 2s ease-in-out infinite;
}
.tans3s {
animation: animationLeftRight 3s ease-in-out infinite;
}
.tans4s {
animation: animationLeftRight 4s ease-in-out infinite;
}
@keyframes animationLeftRight {
0% {
transform: translateX(0px);
}
50% {
transform: translateX(1000px);
}
100% {
transform: translateX(0px);
}
}
@keyframes animationRightLeft {
0% {
transform: translateX(0px);
}
50% {
transform: translateX(-1000px);
}
100% {
transform: translateX(0px);
}
}
</style>

View File

@ -0,0 +1,28 @@
<script lang="ts" setup>
</script>
<template>
<div>
<div class="speechIng">
<img src="@/assets/loading.gif" alt="加载中···">
</div>
</div>
</template>
<style lang="scss" scoped>
.speechIng{
display: flex;
position: fixed;
left: 45%;
top:40%;
z-index: 9999;
// background-color: rgb(23, 21, 19);
}
</style>

View File

@ -0,0 +1,65 @@
<script lang="ts" setup>
import { ref,onBeforeUpdate} from 'vue'
import {ElPagination} from 'element-plus'
const emit = defineEmits(['updateItemList']);
// ()
const props = defineProps({
itemList: {//
type: Array,
default: null
},
count: {//
type: Number,
default: null
},
})
//--------------------------------------------------------------------------------------------------------------------------
const itemList = ref([])//
const limitItemList = ref([])//
const currentPage = ref(1); //
const pageSize = ref(10); //
//
const displayedItemList = () => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
limitItemList.value = itemList.value.slice(startIndex, endIndex);
};
//
function changePage(page:number) {
currentPage.value = page
}
//------------------------------------------------------------------------------------------------------------------------------------
//
onBeforeUpdate(() => {
if (props.count != null&&props.itemList==null) {
emit('updateItemList', currentPage.value)
return
}
itemList.value = props.itemList
displayedItemList()
emit('updateItemList', limitItemList.value)
})
</script>
<template>
<div style="display: flex;justify-content: center;margin-top: 10px;">
<el-pagination layout="prev, pager, next"
:total="props.count == null ? itemList.length : props.count"
:current-page.sync="currentPage"
:page-size="pageSize"
@current-change="changePage"
prev-text="上一页"
next-text="下一页">
</el-pagination>
</div>
</template>
<style scoped>
</style>

16
vite.config.js Normal file
View File

@ -0,0 +1,16 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})