大部分

This commit is contained in:
虾壳 2024-07-17 15:34:57 +08:00
parent 2bbccd9a5f
commit 8f92dc3ed3
26 changed files with 751 additions and 444 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
VITE_APP_BASE_API = 'https://aw.9miao.fun/prod-api'

View File

@ -4,6 +4,7 @@
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="//unpkg.com/@element-plus/icons-vue"></script>
<title>Vite App</title>
</head>
<body>

1
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "zt-sys",
"version": "0.0.0",
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.2",
"element-plus": "^2.7.7",
"pinia": "^2.1.7",

View File

@ -10,6 +10,7 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.2",
"element-plus": "^2.7.7",
"pinia": "^2.1.7",

View File

@ -0,0 +1,25 @@
import http from "@/utils/http";
//获取题目
export const getQuestion= (type)=>{
if(type==null||type==undefined){
type="refresh";
}
return http({
url:"/question/getQuestion",
method:"get",
params:{
type
},
})
}
//注册
export const validationAnswer= (user)=>{
return http({
url:"/question/validationAnswer",
method:"get",
data:user,
headers:{
isToken:false
}
})
}

38
src/api/user/user.js Normal file
View File

@ -0,0 +1,38 @@
import http from "@/utils/http";
//登录
export const login= (user)=>{
return http({
url:"/user/login",
method:"post",
data:user,
headers:{
isToken:false
}
})
}
//注册
export const register= (user)=>{
return http({
url:"/user/register",
method:"post",
data:user,
headers:{
isToken:false
}
})
}
//获取用户信息
export const getUserInfo= ()=>{
return http({
url:"/user",
method:"get"
})
}
//修改用户信息
export const updateUserInfo= (user)=>{
return http.request({
url:"/user/update",
method:"post",
params:user
})
}

BIN
src/assets/image/tibg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
src/assets/image/userbg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

View File

@ -1,44 +0,0 @@
<script setup>
defineProps({
msg: {
type: String,
required: true
}
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

View File

@ -1,88 +0,0 @@
<script setup>
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
you need to test your components and web pages, check out
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
>Cypress Component Testing</a
>.
<br />
More instructions are available in <code>README.md</code>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
Discord server, or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
>StackOverflow</a
>. You should also subscribe to
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
the official
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
twitter account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
</WelcomeItem>
</template>

View File

@ -0,0 +1,63 @@
<template>
<div class="topicInfoBox">
<div class="flex gap-2" style="width: 90%; margin: 0 auto">
<el-tag v-for="itme in props.tags" :key="itme.name" type="primary">{{ itme.name}}</el-tag>&nbsp;
</div>
<div class="introduce">
{{ props.introduce }}
</div>
<p style="width: 90%; margin: 0 auto">
{{ props.topicTitle }}
</p>
<div class="introduce">
<el-radio-group v-model="radio">
<el-radio
v-for="itme in topicSelect"
:key="itme.key"
:value="itme.key"
>{{ itme.key + "." + itme.name }}</el-radio
>
</el-radio-group>
</div>
<div class="introduce">
<el-button v-if="radio != null" type="success">提交</el-button>
</div>
</div>
</template>
<style lang="scss" scoped>
.topicInfoBox {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.introduce {
margin: 10px auto;
width: 90%;
word-break: normal;
word-wrap: break-word;
}
</style>
<script setup>
import { onMounted, ref } from "vue";
const props = defineProps({
id: { type: Number, default: null },
videoUrl: { type: String, default: null },
topicList: {
type: Array,
default: () => {
return [];
},
},
type: { type: Number, default: null },
topicTitle: { type: String, default: "题目" },
introduce: { type: String, default: "介绍" },
tags: { type: Array, default: null },
});
const topicSelect = ref([]);
onMounted(() => {
topicSelect.value = props.topicList;
});
const radio = ref(null);
</script>

View File

@ -0,0 +1,51 @@
<template>
<div class="word-list">
<div
v-for="word in words"
:key="word"
class="word-item"
@click="handleWordClick(word)"
>
{{ word }}
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const emit=defineEmits(['select'])
//
const words = ref([
'高数上', '高数下', '线性代数', '数学分析', '解析几何',
'概率论与数理统计', '复变函数', '实变函数', '常微分方程',
'近世代数', '数据结构', 'C语言', '软件工程类', '大学物理(简明)',
'物理实验', '力学', '热学', '光学', '电磁学', '原子物理学',
'物理专业', '高中物理', '初中物理', '高中化学', '初中化学',
'高中数学', '初中数学', '小学数学', '高中生物', '初中生物',
'有机化学', '无机化学', '分析化学', '物理化学', '结构化学',
'高分子化学', '配位化学', '生物化学', '普通生物学', '细胞生物学',
'遗传学', '微生物学', '植物学', '动物学', '数学', '物理', '化学', '生物',
'法学', '经济学', '其他'
]);
const handleWordClick = (word) => {
console.log(`Clicked word: ${word}`);
emit('select',word);
//
};
</script>
<style scoped>
.word-list {
display: flex;
flex-wrap: wrap;
/* gap: 10px; */
}
.word-item {
padding: 8px;
border-bottom: 1px solid #43CF7C;
cursor: pointer;
user-select: none; /* 防止用户选择文本 */
}
.word-item:hover {
background-color: #f0f0f0;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<div class="video-container">
<video
ref="videoRef"
:src="props.src"
width="100%"
height="400px"
controls
@play="handlePlay"
@fullscreenchange="handleFullscreenChange"
>
您的浏览器不支持 video 标签
</video>
</div>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';
const props = defineProps({
src: String
});
const videoSrc = ref(props.src);
const videoRef = ref(null);
const isFullscreen = ref(false);
const handlePlay = () => {
console.log('视频开始播放');
};
const handleFullscreenChange = () => {
isFullscreen.value = document.fullscreenElement === videoRef.value;
};
watch(() => props.src, (newSrc) => {
videoSrc.value = newSrc;
});
onMounted(() => {
console.log('视频组件挂载完成');
});
onUnmounted(() => {
console.log('视频组件卸载');
});
</script>
<style scoped>
.video-container {
position: relative;
width: 100%;
max-width: 640px; /* 可以根据需要调整 */
/* margin: 0 auto; */
}
button {
position: absolute;
top: 10px;
right: 10px;
z-index: 10;
}
</style>

View File

@ -1,86 +0,0 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

View File

@ -1,19 +0,0 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

View File

@ -6,11 +6,16 @@ import ElementPlus from 'element-plus'
import App from './App.vue'
import router from './router'
// 如果您正在使用CDN引入请删除下面一行。
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus, { size: 'small', zIndex: 3000 })
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.mount('#app')

12
src/utils/Sotrage.js Normal file
View File

@ -0,0 +1,12 @@
const storage ={
setStorage:function(key,value){
window.localStorage.setItem(key,JSON.stringify(value))
},
getStorage:function(key){
return JSON.parse(window.localStorage.getItem(key))
},
removeStorage:function(key){
window.localStorage.removeItem(key)
}
}
export default storage

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

@ -0,0 +1,35 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/el-message.css'
import TokenService from '@/utils/token'
import router from '@/router'
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
const httpInstance=axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 10000
})
// axios请求拦截器
httpInstance.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
if (TokenService.hasToken() && !isToken) {
config.headers['Authorization'] = TokenService.getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, e => Promise.reject(e))
// axios响应式拦截器
httpInstance.interceptors.response.use(res => {
if (res.data.code === 401){
router.push('/login')
}
if(res.data.code==0){
ElMessage.error(res.data.msg)
}
return res.data
}, e => {
return Promise.reject(e)
})
// let abb=1;
export default httpInstance;

View File

@ -1,154 +0,0 @@
import axios from 'axios'
import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus'
import 'element-plus/theme-chalk/el-message.css'
import 'element-plus/theme-chalk/el-message-box.css'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from '@/utils/ruoyi'
import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'
import useUserStore from '@/store/modules/user'
let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制无法进行防重复提交验证。')
return config;
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}
}
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
useUserStore().logOut().then(() => {
location.href = '/index';
})
}).catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
ElMessage({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 601) {
ElMessage({ message: msg, type: 'warning' })
return Promise.reject(new Error(msg))
} else if (code !== 200) {
ElNotification.error({ title: msg })
return Promise.reject('error')
} else {
return Promise.resolve(res.data)
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
}
)
// 通用下载方法
export function download(url, params, filename, config) {
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
}).then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
ElMessage.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
}
export default service

View File

@ -1,8 +1,309 @@
<template>
<div>首页</div>
</template>
<style lang="scss" scoped></style>
<script setup>
</script>
<div class="homeBox">
<div class="nav">
<div><span class="loogText">Xuaua</span></div>
<div>
<el-popover :width="700" trigger="click">
<template #reference>
<div class="selectInput">
<div @click="selectClick">
<el-icon :size="size" :color="color">
<Search />
</el-icon>
</div>
<input type="text" placeholder="搜索关键词" v-model="selectType" />
</div>
</template>
<TypeSelect @select="selectInput"></TypeSelect>
</el-popover>
</div>
<div @click="router.push('/user')">
<el-avatar :size="40" :src="circleUrl" />用户名
</div>
</div>
<div class="main">
<div class="mainBox">
<div class="topicInfo" ref="topicInfoRef" @click="huadong">
<div
ref="topicInfoItemRef"
style="
position: absolute;
height: 100%;
width: 100%;
transition: 0.7s;
"
>
<div
v-for="itme in topicInfo.topicList"
:key="itme.id"
class="topicInfoItem"
>
<TopicBox
:id="itme.id"
:videoUrl="itme.videoUrl"
:introduce="itme.introduce"
:tags="itme.tags"
:topicTitle="itme.topicTitle"
:type="itme.type"
:topicList="itme.xuauaQuestionAnswerList"
/>
</div>
</div>
</div>
<div class="topicAnswer">
<div class="SelectAnswer">
<div @click="get">视频</div>
<div>解析</div>
<div>
<el-icon :size="27" :color="color">
<Star />
</el-icon>
</div>
</div>
<div class="answerInfo">
<VideoBox
:src="topicInfo.topicList[topicInfo.index - 1].videoUrl"
></VideoBox>
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.homeBox {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
.nav {
width: 100%;
height: 8vh;
background-color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
div {
display: flex;
align-items: center;
justify-content: center;
}
.loogText {
font-size: 30px;
margin-left: 10px;
}
}
.main {
width: 100%;
//
flex: 1;
background-color: #ededed;
display: flex;
justify-content: center;
align-items: center;
.mainBox {
width: 97%;
height: 97%;
background-color: #fff;
border-radius: 10px;
display: flex;
justify-content: space-between;
box-sizing: border-box;
padding: 20px;
.topicInfo {
// animation-duration: 2s;
// animation-delay: 250ms;
width: 30%;
height: 100%;
// background-color: #f0f9cc;
overflow: hidden;
position: relative;
background-image: url("@/assets/image/tibg.jpg");
background-size: cover; //
// background-position: center; //
background-repeat: no-repeat; //
background-position: left left;
.topicInfoItem {
// top: -10px;
// animation-duration: 2s;
// animation-delay: 250ms;
height: 100%;
width: 100%;
box-sizing: content-box;
// border: 1px solid red;
}
}
.topicAnswer {
width: 69%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.SelectAnswer {
width: 97%;
margin: 0 auto;
border-bottom: 1px #43cf7c solid;
height: 7%;
display: flex;
align-items: center;
justify-content: space-around;
}
.answerInfo {
width: 100%;
height: 92%;
box-sizing: border-box;
padding: 20px;
// background-color: #f0f9cc;
background-image: url("@/assets/image/tibg.jpg");
background-size: cover; //
background-position: center; //
background-repeat: no-repeat; //
}
// background-color: #F0F9CC;
}
}
}
}
.selectInput {
display: flex;
width: 100%;
height: 100%;
padding: 7px 10px;
border: 2px #000000 solid;
border-radius: 30px;
input {
background-color: #00000000; /* 透明背景 */
border: none; /* 无边框 */
outline: none; /* 去除聚焦时的轮廓 */
padding: 0; /* 无内边距 */
margin: 0; /* 无外边距 */
width: 100%;
}
}
</style>
<script setup>
import { reactive, ref, onMounted } from "vue";
import TopicBox from "@/components/TopicBox.vue";
import VideoBox from "@/components/VideoBox.vue";
import TypeSelect from '@/components/TypeSelect.vue'
import { ElMessage } from "element-plus";
import "element-plus/theme-chalk/el-message.css";
import router from "@/router";
import { getQuestion } from "@/api/question/question";
// import { c } from "vite/dist/node/types.d-aGj9QkWt";
const topicInfoItemRef = ref(null);
const selectType=ref(null);
const topicInfo = reactive({
index: 1,
number: 3,
topicList: [{ id: 1 }, { id: 2 }, { id: 3 }],
});
let mouseDown = false;
const selectInput=(word)=>{
selectType.value=word;
}
const selectClick=async()=>{
let res = await getQuestionInfo(selectType.value);
topicInfo.index=1;
topicInfo.topicList = res;
topicInfo.number = res.length;
}
//
let lastY = null;
//
let currentY = null;
//
const huadong = () => {};
document.addEventListener("mousedown", function (event) {
mouseDown = true;
// lastX = event.clientX;
lastY = event.clientY;
// currentX = event.clientX;
currentY = event.clientY;
// console.log(":", lastX, lastY);
//
document.addEventListener("mousemove", onMouseMove);
});
//
function onMouseMove(event) {
if (mouseDown) {
let deltaY = event.clientY - currentY;
currentY = event.clientY;
setScrollY(deltaY);
}
}
//
document.addEventListener("mouseup", function (event) {
mouseDown = false;
let deltaY = event.clientY - lastY;
console.log("变化", deltaY);
flipPage(deltaY);
document.removeEventListener("mousemove", onMouseMove);
});
const topicInfoRef = ref(null);
const get = () => {
console.log("信息", getScrollbarPosition());
};
const setScrollY = (y) => {
if (topicInfoRef.value) {
topicInfoRef.value.scrollTop = topicInfoRef.value.scrollTop - y;
}
};
const flipPage = (y) => {
if (topicInfoItemRef.value) {
topicInfoRef.value.scrollTop = 0;
if (y < -80) {
topicInfo.index++;
if (topicInfo.index > topicInfo.number) {
topicInfo.index = topicInfo.number;
ElMessage({ message: "没有更多了", type: "warning" });
}
if (topicInfo.index == topicInfo.number) {
//
// topicInfo.number+=3;
// topicInfo.topicList=[...topicInfo.topicList,...[{id:topicInfo.number+1},{id:topicInfo.number+2},{id:topicInfo.number+3}
// ]]
}
}
else if (y > 80) {
topicInfo.index--;
if (topicInfo.index == 0) {
topicInfo.index = 1;
ElMessage({ message: "到顶了", type: "warning" });
}
}
// alert("123")
topicInfoItemRef.value.style.top =
-(topicInfo.index - 1) * topicInfoItemRef.value.clientHeight + "px";
}
};
const getScrollbarPosition = () => {
if (topicInfoRef.value) {
const element = topicInfoRef.value;
return {
scrollLeft: element.scrollLeft, //
scrollTop: element.scrollTop, //
scrollWidth: element.scrollWidth, //
scrollHeight: element.scrollHeight, //
clientWidth: element.clientWidth, //
clientHeight: element.clientHeight, //
};
}
return null;
};
const getQuestionInfo = async (type) => {
let res = await getQuestion(type);
console.log("res", res);
return res.data;
};
onMounted(async () => {
let res = await getQuestionInfo();
topicInfo.index=1;
topicInfo.topicList = res;
topicInfo.number = res.length;
console.log("topicInfo", topicInfo);
});
</script>

View File

@ -5,7 +5,7 @@
<div class="inputBox">密码<input type="password" v-model="user.password" /></div>
<div class="checkBox"><el-checkbox v-model="remember"></el-checkbox>记住密码</div>
<div class="btnBox">
<el-button size="large" type="success" round @click="login">登录</el-button>
<el-button size="large" type="success" round @click="toLogin">登录</el-button>
<el-button
size="large"
type="danger"
@ -121,19 +121,37 @@ input:focus {
<script setup>
import {ElMessage} from "element-plus";
import 'element-plus/theme-chalk/el-message.css'
import 'element-plus/theme-chalk/el-message-box.css'
import { ref, reactive } from "vue";
import { login , register } from "@/api/user/user";
import storage from '@/utils/Sotrage'
import TokenService from '@/utils/token'
import router from '@/router'
import { ref, reactive,onMounted } from "vue";
const user=reactive({
username: "",
password: "",
});
//
const remember = ref(false);
const login = () => {
if(user.username===""||user.password===""){
ElMessage({ message: "请输入完整信息", type: 'warning' })
}
const toLogin = async () => {
console.log(user);
if(user.username===""||user.username===null||user.password===""||user.password===null){
ElMessage({ message: "请输入完整信息", type: 'warning' })
}
let res=await login(user);
if(res.code===200){
ElMessage({ message: "登录成功", type: 'success' })
if(remember.value){
storage.setStorage("username",user.username);
storage.setStorage("password",user.password);
}else{
storage.removeStorage("username");
storage.removeStorage("password");
}
TokenService.saveToken(res.token)
//
router.push('/');
}
};
//
const dialogFormVisible = ref(false);
@ -146,10 +164,10 @@ const ruleForm = reactive({
});
//
const usernamevalidate = (rule, value, callback) => {
if (value === "") {
if (value === ""||value==null) {
callback(new Error("请输入账号"));
callback();
}
callback();
};
const validatePass = (rule, value, callback) => {
if (value === "") {
@ -189,13 +207,25 @@ const rules = reactive({
//
const submitForm = (formEl) => {
if (!formEl) return;
formEl.validate((valid) => {
formEl.validate(async(valid) => {
if (valid) {
console.log("submit!");
// console.log("submit!");
let res =await register({username:ruleForm.username,password:ruleForm.password});
if(res.code===200){
ElMessage({ message: "注册成功", type: 'success' })
dialogFormVisible.value=false;
}else{
ElMessage({ message: res.msg, type: 'error' })
}
}
else {
console.log("error submit!");
}
});
};
onMounted(() => {
console.log("onMounted");
user.username=storage.getStorage("username");
user.password=storage.getStorage("password");
});
</script>

View File

@ -1,8 +1,108 @@
<template>
<div>用户</div>
</template>
<style lang="scss" scoped></style>
<script setup>
</script>
<div class="userBox">
<div class="userInfo">
<div class="userT">
<el-avatar :size="150" :src="circleUrl" />
</div>
<div class="userName">用户名</div>
<div class="userName" @click="router.push('/');">返回首页</div>
</div>
<div class="collectionTopic">
<div class="title">收藏</div>
<div class="collectionList">
<div class="collectionTme"></div>
<div class="collectionTme"></div>
<div class="collectionTme"></div>
<div class="collectionTme"></div>
<div class="collectionTme"></div>
<div class="collectionTme"></div>
<div class="collectionTme"></div>
<div class="collectionTme"></div>
<!-- <div class="collectionTme"></div> -->
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.userBox {
width: 100vw;
height: 100vh;
background-image: url("@/assets/image/userbg.jpg");
background-size: cover; //
background-position: center; //
background-repeat: no-repeat; //
display: flex;
justify-content: center;
align-items: center;
.userInfo {
height: 90%;
width: 20%;
// background-color: #fff;
display: flex;
flex-direction: column;
// justify-content: space-between;
align-items: center;
.userName {
margin-top: 30px;
font-size: 30px;
}
.userT {
margin-top: 90px;
}
}
.collectionTopic {
margin-left: 30px;
width: 60%;
height: 90%;
// background-color: #ff0;
display: flex;
flex-direction: column;
// justify-content: space-between;
align-items: center;
.title {
margin-top: 10px;
font-size: 20px;
display: flex;
justify-content: center;
align-items: center;
width: 90%;
height: 5%;
background-color: #d2fce3;
border-radius: 70px;
}
.collectionList {
overflow-y: auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
flex: 1;
width: 90%;
box-sizing: border-box;
display: flex;
// justify-content: space-between;
flex-wrap: wrap;
padding: 10px;
.collectionTme {
/* 宽度是固定好的 */
width: 30%;
height: 47%;
background-color: #D2FCE3;
margin-bottom: 10px;
// border: 1px solid red;
/*这个数值需要自己去调*/
margin-right: 17px;
}
}
.collectionList:after {
content: "";
flex: auto;
}
}
}
</style>
<script setup>
import router from '@/router'
</script>