初始
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
8
env.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// env.d.ts
|
||||
// 这个文件生效的前提是 tsconfig.json 中配置了 "include": ["env.d.ts"],
|
||||
declare module "*.vue" {
|
||||
import { DefineComponent } from "vue";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
BIN
help-management-myself.zip
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<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.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
4496
package-lock.json
generated
Normal file
38
package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "help-management-myself",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port 5174",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.12.1",
|
||||
"echarts": "^5.6.0",
|
||||
"element-plus": "^2.11.2",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.5.0",
|
||||
"vue": "^3.5.18",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-router": "^4.5.1",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.3",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"less": "^4.4.1",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.8.3",
|
||||
"unplugin-auto-import": "^20.1.0",
|
||||
"unplugin-vue-components": "^29.0.0",
|
||||
"vite": "^7.1.2",
|
||||
"vue-tsc": "^3.0.5"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
45
src/App.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="main_bg w-[100vw] h-[100vh]">
|
||||
<TopHeader class="fixed top-0"></TopHeader>
|
||||
<Header class="mt-[96px] px-[40px]" v-if="route.path == '/'"></Header>
|
||||
<router-view />
|
||||
</div>
|
||||
<!-- <div>
|
||||
<a href="https://vite.dev" target="_blank">
|
||||
<img src="/vite.svg" class="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://vuejs.org/" target="_blank">
|
||||
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
|
||||
</a>
|
||||
</div>
|
||||
<HelloWorld msg="Vite + Vue" /> -->
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import HelloWorld from "./components/HelloWorld.vue";
|
||||
import TopHeader from "./components/TopHeader.vue";
|
||||
import Header from "./components/Header.vue";
|
||||
|
||||
const route = useRoute();
|
||||
console.log("route.fullpath", route.path);
|
||||
</script>
|
||||
<style scoped>
|
||||
.main_bg {
|
||||
background: url("./assets/web/mainBG.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* .logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #42b883aa);
|
||||
} */
|
||||
</style>
|
||||
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
BIN
src/assets/web/300.jpg
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
src/assets/web/avatar.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
src/assets/web/banner.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
src/assets/web/digimg.png
Normal file
|
After Width: | Height: | Size: 520 KiB |
BIN
src/assets/web/mainBG.png
Normal file
|
After Width: | Height: | Size: 410 KiB |
BIN
src/assets/web/noData.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
src/assets/web/statisTop.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
src/assets/web/statistic.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
src/assets/web/weblogo1.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
78
src/auto-import.d.ts
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const isShallow: typeof import('vue')['isShallow']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
17
src/components.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Header: typeof import('./components/Header.vue')['default']
|
||||
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
TopHeader: typeof import('./components/TopHeader.vue')['default']
|
||||
}
|
||||
}
|
||||
50
src/components/Header.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="bg-transparant">
|
||||
<a-menu v-model:selectedKeys="current" mode="horizontal">
|
||||
<a-menu-item v-for="item in departmentList" :key="item.key">
|
||||
{{ item.name }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch } from "vue";
|
||||
import { useUserStore } from "@/store/user.ts";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
|
||||
let userStore: any = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
const current = ref<string[]>(["3"]);
|
||||
watch(
|
||||
current,
|
||||
(N, O) => {
|
||||
if (N == 5) {
|
||||
router.push({
|
||||
path: "/statistic",
|
||||
});
|
||||
}
|
||||
if (N != 5) {
|
||||
userStore.SetMenuInfo({ key: current.value });
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
let departmentList = [
|
||||
{ key: "3", name: "人工智能系" },
|
||||
|
||||
{ key: "1", name: "电子信息工程系" },
|
||||
{ key: "2", name: "通信工程系" },
|
||||
// { key: "4", name: "本科实验教学中心" },
|
||||
{ key: "5", name: "数据统计" },
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ant-menu-light {
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
41
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
39
src/components/TopHeader.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="w-full flex justify-between py-[18px] px-[40px] bg-white">
|
||||
<div>
|
||||
<img
|
||||
src="../assets/web/statisTop.png"
|
||||
alt=""
|
||||
class="w-[167px] h-[60px]"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="userStore.userInfo && userStore.userInfo.data"
|
||||
class="h-[48px] flex py-[6px] px-[14px] bg-[#F6F8FA] rounded-[10px]"
|
||||
>
|
||||
<div class="avatar"></div>
|
||||
<div class="leading-[34px] ml-[10px]">
|
||||
{{ userStore.userInfo.data.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch } from "vue";
|
||||
import { useUserStore } from "@/store/user.ts";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
|
||||
let userStore: any = useUserStore();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.avatar {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
background: url("../assets/web/avatar.jpg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
25
src/main.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createApp } from "vue";
|
||||
import "./style.css";
|
||||
import App from "./App.vue";
|
||||
import router from "./routes/index";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
import Antd from "ant-design-vue";
|
||||
|
||||
import * as echarts from "echarts";
|
||||
// import "ant-design-vue/dist/antd.css";
|
||||
import "ant-design-vue/dist/reset.css";
|
||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||
import ElementPlus from "element-plus";
|
||||
import "element-plus/dist/index.css";
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate); // 将插件添加到 pinia 实例上
|
||||
const app = createApp(App);
|
||||
app.config.globalProperties.$echarts = echarts;
|
||||
//routes
|
||||
app.use(router);
|
||||
|
||||
app.use(pinia);
|
||||
app.use(ElementPlus);
|
||||
app.use(Antd);
|
||||
app.mount("#app");
|
||||
117
src/request/api.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import instance from "./request";
|
||||
import axios from "axios";
|
||||
|
||||
//一般情况下,接口类型会放到一个文件
|
||||
// 下面两个TS接口,表示要传的参数
|
||||
interface ReqLogin {
|
||||
name: string;
|
||||
paw: string;
|
||||
}
|
||||
interface ReqStatus {
|
||||
id: string;
|
||||
navStatus: string;
|
||||
}
|
||||
|
||||
// Res是返回的参数,T是泛型,需要自己定义,返回对数统一管理***
|
||||
type Res<T> = Promise<ItypeAPI<T>>;
|
||||
// 一般情况下响应数据返回的这三个参数,
|
||||
// 但不排除后端返回其它的可能性,
|
||||
interface ItypeAPI<T> {
|
||||
data: T; //请求的数据,用泛型
|
||||
msg: string | null; // 返回状态码的信息,如请求成功等
|
||||
code: number; //返回后端自定义的200,404,500这种状态码
|
||||
}
|
||||
|
||||
// post请求 ,没参数
|
||||
export const LogoutAPI = (): Res<null> => instance.post("/admin/logout");
|
||||
|
||||
// post请求,有参数,如传用户名和密码
|
||||
export const loginAPI = (data: any): Res<any> =>
|
||||
instance.post(
|
||||
`/basic-api/auth/login?teacherId=${data.teacherId}&password=${data.password}`,
|
||||
{}
|
||||
);
|
||||
|
||||
// post请求 ,没参数,但要路径传参
|
||||
export const StatusAPI = (data: ReqStatus): Res<null> =>
|
||||
instance.post(`/productCategory?ids=${data.id}&navStatus=${data.navStatus}`);
|
||||
|
||||
// get请求,没参数,
|
||||
export const getTeacherListApi = (params: any): Res<null> =>
|
||||
instance.get(`/basic-api/mentors/lsit?id=${params}`);
|
||||
export const getStuListApi = (params: any) =>
|
||||
instance.get(`/basic-api/mentors/studentList?teacherId=${params.teacherId}`);
|
||||
//获取用户信息
|
||||
export const getUserInfoApi = () =>
|
||||
instance.get("/basic-api/auth/getLoginUser");
|
||||
//获取学生详情
|
||||
export const getStuInfoApi = (params: any) =>
|
||||
instance.get(`/basic-api/students/detail?id=${params}`);
|
||||
//获取教师详情
|
||||
export const getTeacherInfoApi = (params: any) =>
|
||||
instance.get(`/basic-api/mentors/detail?id=${params}`);
|
||||
//获取教师dify
|
||||
export const getTeacherDifyApi = (params: any) =>
|
||||
instance.post(`/basic-api/mentors/dify`, {
|
||||
query: params,
|
||||
user: 1,
|
||||
});
|
||||
//获取学生dify
|
||||
export const getStudentDifyApi = (params: any) =>
|
||||
instance.post(`/basic-api/students/dify`, {
|
||||
query: params,
|
||||
user: 1,
|
||||
});
|
||||
// 获取学生评估
|
||||
export const getStuDetailApi = (data: any): Res<any> => {
|
||||
const body = {
|
||||
inputs: {},
|
||||
query: "人工智能",
|
||||
response_mode: "streaming" as const,
|
||||
conversation_id: "",
|
||||
user: "abc-123",
|
||||
files: [],
|
||||
};
|
||||
// 创建axios实例
|
||||
let request = axios.create({
|
||||
baseURL: "http://zb89ba6d.natappfree.cc", // 所有的请求地址前缀部分
|
||||
timeout: 80000, // 请求超时时间(毫秒)
|
||||
withCredentials: true, // 异步请求携带cookie
|
||||
headers: {
|
||||
// 设置后端需要的传参类型
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer app-XVjctRwhZq3w8ijo87x7hRc0`,
|
||||
},
|
||||
});
|
||||
request.post("/v1/chat-messages");
|
||||
|
||||
// instance.post(`/ai-basic-api/v1/chat-messages`, body);
|
||||
// const body = {
|
||||
// inputs: {},
|
||||
// query: "人工智能",
|
||||
// response_mode: "streaming" as const,
|
||||
// conversation_id: "",
|
||||
// user: "abc-123",
|
||||
// files: [],
|
||||
// };
|
||||
// const response = fetch(`https://hn.dify.holo-land.com/v1/chat-messages`, {
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// Authorization: `Bearer app-XVjctRwhZq3w8ijo87x7hRc0`,
|
||||
// },
|
||||
// body: JSON.stringify(body),
|
||||
// });
|
||||
};
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// ...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
|
||||
// },
|
||||
|
||||
// get请求,有参数,路径也要传参 (也可能直接在这写类型,不过不建议,大点的项目会维护一麻烦)
|
||||
export const ProductCategoryApi = (params: { parentId: number }): any =>
|
||||
instance.get(`/productCategory/list/${params.parentId}`, { params });
|
||||
|
||||
// get请求,有参数,(如果你不会写类型也可以使用any,不过不建议,因为用了之后 和没写TS一样)
|
||||
export const AdminListAPI = (params: any): any =>
|
||||
instance.get("/admin/list", { params });
|
||||
68
src/request/request.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import axios from "axios";
|
||||
import { useUserStore } from "@/store/user.ts";
|
||||
import { ElMessage } from "element-plus";
|
||||
let userStore: any = useUserStore();
|
||||
|
||||
// 创建axios实例
|
||||
const request = axios.create({
|
||||
baseURL: "", // 所有的请求地址前缀部分
|
||||
timeout: 80000, // 请求超时时间(毫秒)
|
||||
withCredentials: true, // 异步请求携带cookie
|
||||
// headers: {
|
||||
// 设置后端需要的传参类型
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'token': x-auth-token',//一开始就要token
|
||||
// 'X-Requested-With': 'XMLHttpRequest',
|
||||
// },
|
||||
});
|
||||
|
||||
// request拦截器
|
||||
request.interceptors.request.use(
|
||||
(config) => {
|
||||
// console.log("tore.userInfo.data1", userStore?.userInfo?.data);
|
||||
|
||||
if (userStore?.userInfo?.data?.auth) {
|
||||
config.headers["Authorization"] =
|
||||
"Bearer " + userStore.userInfo.data.auth;
|
||||
// }
|
||||
}
|
||||
// 如果你要去localStor获取token,(如果你有)
|
||||
// let token = localStorage.getItem("x-auth-token");
|
||||
// if (token) {
|
||||
//添加请求头
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
console.log("response1");
|
||||
ElMessage.error(`${error.message}`);
|
||||
|
||||
// 对请求错误做些什么
|
||||
Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// response 拦截器
|
||||
request.interceptors.response.use(
|
||||
(response) => {
|
||||
console.log("response", response);
|
||||
|
||||
return response.data;
|
||||
},
|
||||
(error) => {
|
||||
// 对响应错误做点什么
|
||||
console.log("error.status11", error);
|
||||
ElMessage.error(`${error.message}`);
|
||||
|
||||
if (error.status != 200) {
|
||||
console.log("error.status1", error.status);
|
||||
console.log("userStore", userStore);
|
||||
|
||||
//删除token
|
||||
//页面跳转 这里写了一个斜杠是因为路由里面做了一个重定向跳转
|
||||
window.location.href = "/login";
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
export default request;
|
||||
30
src/routes/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
let routes = [
|
||||
// {
|
||||
// path: "/",
|
||||
// name: "home1",
|
||||
// //使用import可以路由懒加载,如果不使用,太多组件一起加载会造成白屏
|
||||
// component: () => import("../view/homeView.vue"),
|
||||
// },
|
||||
{ path: "/", component: () => import("../view/homeView.vue") },
|
||||
{ path: "/login", component: () => import("../view/login/index.vue") },
|
||||
{
|
||||
path: "/statistic",
|
||||
component: () => import("../view/statistic/index.vue"),
|
||||
},
|
||||
|
||||
//{
|
||||
//配置404页面
|
||||
//path: '/:catchAll(.*)',
|
||||
//name: '404',
|
||||
//component: () => import(''),
|
||||
//}
|
||||
];
|
||||
// 路由
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
});
|
||||
// 导出
|
||||
export default router;
|
||||
17
src/store/user.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useUserStore = defineStore("user", {
|
||||
state: () => ({
|
||||
userInfo: {},
|
||||
menuInfo: {},
|
||||
}),
|
||||
actions: {
|
||||
SetUserInfo(data: any) {
|
||||
this.userInfo = data;
|
||||
},
|
||||
SetMenuInfo(data: any) {
|
||||
this.menuInfo = data;
|
||||
},
|
||||
},
|
||||
persist: true, // 整个store都会被持久化
|
||||
});
|
||||
83
src/style.css
Normal file
@@ -0,0 +1,83 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
} */
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
/* max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center; */
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
599
src/view/homeView.vue
Normal file
@@ -0,0 +1,599 @@
|
||||
<template>
|
||||
<div v-loading="loading" class="h-[calc(100vh-144px)] flex px-[40px]">
|
||||
<div
|
||||
v-if="!(teacherList.data && teacherList.data.length)"
|
||||
class="w-full h-full bg-white relative flex justify-center items-center"
|
||||
>
|
||||
<div class="no_data"></div>
|
||||
</div>
|
||||
<a-menu
|
||||
v-if="teacherList.data && teacherList.data.length"
|
||||
id="dddddd"
|
||||
style="width: 256px"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
mode="inline"
|
||||
class="h-full overflow-y-scroll mr-[20px]"
|
||||
>
|
||||
<a-menu-item
|
||||
v-for="item in teacherList.data"
|
||||
:key="item.teacherId"
|
||||
@titleClick="titleClick"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<div class="flex justify-between">
|
||||
<div>{{ item.name }}</div>
|
||||
<div
|
||||
@click="showTeacherDetail"
|
||||
v-if="
|
||||
item.teacherId == selectedKeys[0] &&
|
||||
userStore.userInfo.data &&
|
||||
userStore.userInfo.data.isLeader
|
||||
"
|
||||
class="mr-[20px]"
|
||||
>
|
||||
详情
|
||||
</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
|
||||
<div
|
||||
v-if="teacherList.data && teacherList.data.length"
|
||||
class="w-[calc(100%-280px)] h-full"
|
||||
>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="stuList.data"
|
||||
:scroll="{ y: 'calc(100vh - 264px)' }"
|
||||
:pagination="{ pageSize: 50 }"
|
||||
class="h-[calc(100%-300px)]"
|
||||
>
|
||||
<template #action="{ record }">
|
||||
<span
|
||||
@click="toStudetail(record)"
|
||||
class="text-[#1677ff] cursor-pointer"
|
||||
>
|
||||
详情
|
||||
</span>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="stuDetailVisible"
|
||||
:close-on-click-modal="false"
|
||||
title="学生详情"
|
||||
width="800"
|
||||
class="stu_detail"
|
||||
>
|
||||
<div class="w-full text-[16px]">
|
||||
<div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">姓名:</div>
|
||||
<div class="text-[#666]">{{ stuDetail.data.name || "-" }}</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">学生号:</div>
|
||||
<div class="text-[#666]">{{ stuDetail.data.studentId || "-" }}</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">性别:</div>
|
||||
<div class="text-[#666]">{{ stuDetail.data.gender || "-" }}</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">院系:</div>
|
||||
<div class="text-[#666]">
|
||||
{{
|
||||
(stuDetail.data.department && stuDetail.data.department.name) ||
|
||||
"-"
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">专业:</div>
|
||||
<div class="text-[#666]">
|
||||
{{ (stuDetail.data.major && stuDetail.data.major.name) || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">班级:</div>
|
||||
<div class="text-[#666]">
|
||||
{{
|
||||
(stuDetail.data.academicClass &&
|
||||
stuDetail.data.academicClass.name) ||
|
||||
"-"
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">书院:</div>
|
||||
<div class="text-[#666]">
|
||||
{{
|
||||
(stuDetail.data.college && stuDetail.data.college.name) || "-"
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">班级:</div>
|
||||
<div class="text-[#666]">
|
||||
{{
|
||||
(stuDetail.data.collegeClass &&
|
||||
stuDetail.data.collegeClass.name) ||
|
||||
"-"
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-[10px]">学生评估:</div>
|
||||
<el-input
|
||||
v-model="stuDetail.data.evaluation"
|
||||
style="width: 100%"
|
||||
:rows="12"
|
||||
type="textarea"
|
||||
placeholder="评估中"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="teacherDetailVisible"
|
||||
:close-on-click-modal="false"
|
||||
title="教师详情"
|
||||
width="800"
|
||||
class="teacher_detail"
|
||||
>
|
||||
<div class="w-full text-[16px]">
|
||||
<div v-if="teacherDail.data">
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">姓名:</div>
|
||||
<div class="text-[#666]">
|
||||
{{ teacherDail.data.name || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">职工号:</div>
|
||||
<div class="text-[#666]">
|
||||
{{ teacherDail.data.teacherId || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">职性别:</div>
|
||||
<div class="text-[#666]">
|
||||
{{ teacherDail.data.gender || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">出生年份:</div>
|
||||
<div class="text-[#666]">
|
||||
{{ teacherDail.data.birthYear || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">职称:</div>
|
||||
<div class="text-[#666]">
|
||||
{{ teacherDail.data.title || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">学历:</div>
|
||||
<div class="text-[#666]">
|
||||
{{ teacherDail.data.education || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">毕业院校:</div>
|
||||
<div class="text-[#666]">
|
||||
{{ teacherDail.data.graduationSchool || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="text-[#333]">联系方式:</div>
|
||||
<div class="text-[#666]">
|
||||
{{ teacherDail.data.telphone || "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="flex-shrink-0 text-[#333]">著作:</div>
|
||||
<div
|
||||
v-if="teacherDail.data.books && teacherDail.data.books.length"
|
||||
class="text-[#666]"
|
||||
>
|
||||
{{
|
||||
teacherDail.data.books.map((item) => item.bookTitle).join("、")
|
||||
}}
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="flex-shrink-0 text-[#333]">专利:</div>
|
||||
<div
|
||||
v-if="teacherDail.data.patents && teacherDail.data.patents.length"
|
||||
class="text-[#666]"
|
||||
>
|
||||
{{
|
||||
teacherDail.data.patents
|
||||
.map((item) => item.patentName)
|
||||
.join("、")
|
||||
}}
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="flex-shrink-0 text-[#333]">授课科目:</div>
|
||||
<div
|
||||
v-if="
|
||||
teacherDail.data.subjects && teacherDail.data.subjects.length
|
||||
"
|
||||
class="text-[#666]"
|
||||
>
|
||||
{{
|
||||
teacherDail.data.subjects
|
||||
.map((item) => item.subjectName)
|
||||
.join("、")
|
||||
}}
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="flex-shrink-0 text-[#333]">科研项目:</div>
|
||||
<div
|
||||
v-if="
|
||||
teacherDail.data.researchProjects &&
|
||||
teacherDail.data.researchProjects.length
|
||||
"
|
||||
class="text-[#666]"
|
||||
>
|
||||
{{
|
||||
teacherDail.data.researchProjects
|
||||
.map((item) => item.projectName)
|
||||
.join("、")
|
||||
}}
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="flex-shrink-0 text-[#333]">科研奖励:</div>
|
||||
<div
|
||||
v-if="
|
||||
teacherDail.data.teachingAwards &&
|
||||
teacherDail.data.teachingAwards.length
|
||||
"
|
||||
class="text-[#666]"
|
||||
>
|
||||
{{
|
||||
teacherDail.data.teachingAwards
|
||||
.map((item) => item.projectName)
|
||||
.join("、")
|
||||
}}
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</div>
|
||||
<div class="flex mb-[10px]">
|
||||
<div class="flex-shrink-0 text-[#333]">科研论文:</div>
|
||||
<div
|
||||
v-if="
|
||||
teacherDail.data.researchPapers &&
|
||||
teacherDail.data.researchPapers.length
|
||||
"
|
||||
class="pre_wrap text-[#666]"
|
||||
>
|
||||
{{
|
||||
teacherDail.data.researchPapers
|
||||
.map((item) => item.paperTitle)
|
||||
.join(";\n")
|
||||
}}
|
||||
</div>
|
||||
<div v-else>-</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-[10px]">教师评估:</div>
|
||||
<el-input
|
||||
v-model="teacherDail.data.evaluation"
|
||||
style="width: 100%"
|
||||
:rows="6"
|
||||
type="textarea"
|
||||
placeholder="评估中"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { fetchEventSource } from "@microsoft/fetch-event-source";
|
||||
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { watch } from "vue";
|
||||
|
||||
import {
|
||||
loginAPI,
|
||||
getTeacherListApi,
|
||||
getStuListApi,
|
||||
getStuInfoApi,
|
||||
getTeacherInfoApi,
|
||||
getTeacherDifyApi,
|
||||
getStuDetailApi,
|
||||
getStudentDifyApi,
|
||||
} from "@/request/api";
|
||||
import { useUserStore } from "@/store/user.ts";
|
||||
import { ElMessage } from "element-plus";
|
||||
let loading = ref(false);
|
||||
let userStore: any = useUserStore();
|
||||
const stuDetailVisible: any = ref(false);
|
||||
const stuDetail: any = reactive({ data: {} });
|
||||
|
||||
const selectedKeys = ref([]);
|
||||
// const openKeys = ref(["sub1"]);
|
||||
const columns = [
|
||||
{
|
||||
title: "学生号",
|
||||
dataIndex: "studentId",
|
||||
key: "studentId",
|
||||
},
|
||||
{
|
||||
title: "姓名",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "年级",
|
||||
dataIndex: "currentGrade",
|
||||
key: "currentGrade",
|
||||
},
|
||||
{
|
||||
title: "院系",
|
||||
dataIndex: "departmentName",
|
||||
key: "departmentName",
|
||||
},
|
||||
{
|
||||
title: "专业",
|
||||
dataIndex: "majorName",
|
||||
key: "majorName",
|
||||
},
|
||||
{
|
||||
title: "书院",
|
||||
dataIndex: "collegeName",
|
||||
key: "collegeName",
|
||||
},
|
||||
{
|
||||
title: "班级",
|
||||
dataIndex: "collegeClassName",
|
||||
key: "collegeClassName",
|
||||
},
|
||||
{
|
||||
title: "详情",
|
||||
key: "action",
|
||||
slots: { customRender: "action" },
|
||||
},
|
||||
];
|
||||
const teacherList = reactive({
|
||||
data: [],
|
||||
});
|
||||
const teacherDetailVisible: any = ref(false);
|
||||
|
||||
const teacherDail = reactive({ data: { evaluation: "" } });
|
||||
const stuList = reactive({
|
||||
data: [],
|
||||
});
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const nowSelecTeacher = reactive({ data: {} });
|
||||
watch(
|
||||
() => userStore.menuInfo,
|
||||
(N, O) => {
|
||||
getTeacherList();
|
||||
console.log("menuInfo1111111", userStore.menuInfo.key);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
selectedKeys,
|
||||
() => {
|
||||
getStuList();
|
||||
},
|
||||
{
|
||||
immediate: false,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
onMounted(async () => {
|
||||
getTeacherList();
|
||||
});
|
||||
onActivated(async () => {
|
||||
getTeacherList();
|
||||
});
|
||||
let ipAddress = "http://zb89ba6d.natappfree.cc";
|
||||
|
||||
// async function getStuDetail() {
|
||||
// let res = await getStuDetailApi();
|
||||
// console.log("学生详情", res);
|
||||
// }
|
||||
|
||||
async function getAIDetail(sseUrl: any, query: any) {
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
fetchEventSource(sseUrl, {
|
||||
method: "POST",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "text/event-stream",
|
||||
Authorization: "Bearer " + userStore.userInfo.data.auth,
|
||||
// credentials: "include",
|
||||
},
|
||||
credentials: "include",
|
||||
// body: mes.replace(/\\/g, '\\\\'),
|
||||
body: JSON.stringify({ query: JSON.stringify(query), user: "1" }),
|
||||
signal,
|
||||
openWhenHidden: true,
|
||||
onmessage(e) {
|
||||
console.log("e", e);
|
||||
let temdata = "";
|
||||
const chunk = e.data;
|
||||
if (chunk === "[DONE]") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
temdata += chunk;
|
||||
console.log("temdata", temdata);
|
||||
} catch (error) {
|
||||
console.error("Message update failed:", error);
|
||||
controller.abort();
|
||||
}
|
||||
},
|
||||
onclose() {
|
||||
console.log("Connection closed");
|
||||
controller.abort();
|
||||
},
|
||||
onerror(error) {
|
||||
console.error("Error----------------->:", error);
|
||||
controller.abort();
|
||||
throw error;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function toStudetail(data) {
|
||||
console.log("data", data);
|
||||
loading.value = true;
|
||||
let res = await getStuInfoApi(data.id);
|
||||
loading.value = false;
|
||||
stuDetailVisible.value = true;
|
||||
|
||||
// let res2 = await getTeacherDifyApi(JSON.stringify(res.data));
|
||||
|
||||
// getAIDetail(ipAddress + `/mentors/dify`, res.data);
|
||||
|
||||
if (res.code == 200) {
|
||||
// res.data.academicClassName = res.data?.academicClass?.name || "-";
|
||||
// res.data.evaluation = res2.data;
|
||||
stuDetail.data = res.data;
|
||||
console.log("stuDetail.data", stuDetail.data);
|
||||
|
||||
console.log("res.data", JSON.stringify(res.data));
|
||||
}
|
||||
let res2 = await getStudentDifyApi(JSON.stringify(res.data));
|
||||
stuDetail.data.evaluation = res2.data;
|
||||
console.log("res2.data");
|
||||
|
||||
console.log("stuDetail.data", stuDetail.data);
|
||||
|
||||
// router.push({
|
||||
// path: "/stu-detail",
|
||||
// query: {
|
||||
// id: data.studentId,
|
||||
// },
|
||||
// });
|
||||
}
|
||||
async function getTeacherList() {
|
||||
let res = await getTeacherListApi(userStore.menuInfo.key);
|
||||
console.log("res", res);
|
||||
if (res?.data) {
|
||||
teacherList.data.splice(0);
|
||||
selectedKeys.value[0] = res?.data[0]?.teacherId || null;
|
||||
nowSelecTeacher.data = res?.data[0] || {};
|
||||
|
||||
teacherList.data.push(...res?.data);
|
||||
}
|
||||
}
|
||||
async function getStuList() {
|
||||
console.log("getStuList", selectedKeys.value);
|
||||
let res = await getStuListApi({ teacherId: selectedKeys.value[0] });
|
||||
stuList.data.splice(0);
|
||||
|
||||
if (res?.data?.students?.length) {
|
||||
res?.data?.students.forEach((item) => {
|
||||
item.collegeClassName = item?.collegeClass?.name || "-";
|
||||
item.collegeName = item?.college?.name || "-";
|
||||
item.majorName = item?.major?.name || "-";
|
||||
item.departmentName = item?.department?.name || "-";
|
||||
// item.collegeClassName = item.collegeClass.name;
|
||||
// item.collegeClassName = item.collegeClass.name;
|
||||
});
|
||||
stuList.data.push(...res.data.students);
|
||||
}
|
||||
}
|
||||
async function showTeacherDetail() {
|
||||
loading.value = true;
|
||||
let res = await getTeacherInfoApi(nowSelecTeacher.data.id);
|
||||
loading.value = false;
|
||||
|
||||
if (res.code == 200) {
|
||||
if (res.data) {
|
||||
console.log("res.data", JSON.stringify(res.data));
|
||||
|
||||
teacherDail.data = res.data;
|
||||
}
|
||||
|
||||
teacherDetailVisible.value = true;
|
||||
// console.log("teacherDetailVisible.value", teacherDetailVisible.value);
|
||||
} else {
|
||||
ElMessage.error("获取数据失败,请稍后重试!");
|
||||
}
|
||||
let resDify = await getTeacherDifyApi(JSON.stringify(res.data));
|
||||
teacherDail.data.evaluation = resDify.data;
|
||||
}
|
||||
function handleClick(data) {
|
||||
nowSelecTeacher.data = data;
|
||||
}
|
||||
const titleClick = (e: Event) => {
|
||||
console.log("titleClick", e);
|
||||
};
|
||||
function toAboutPage() {
|
||||
router.push({
|
||||
path: "/about",
|
||||
query: {
|
||||
title: "666",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function toTestPage() {
|
||||
router.push({
|
||||
path: "/test/" + 888,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.no_data {
|
||||
background: url("../assets/web/noData.png");
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
// background-image: url("../assets/web/noData.png");
|
||||
// background-repeat: no-repeat;
|
||||
// background-color: #333;
|
||||
// position: absolute;
|
||||
// left: 50%;
|
||||
// top: 50%;
|
||||
z-index: 100;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
.pre_wrap {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
// .ant-table-wrapper {
|
||||
// height: 100%;
|
||||
// }
|
||||
// :deep(.ant-spin-nested-loading) {
|
||||
// height: 100%;
|
||||
// }
|
||||
// :deep(.ant-spin-container) {
|
||||
// height: 100%;
|
||||
// }
|
||||
:deep(.ant-menu-item) {
|
||||
// padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
:deep(.stu_detail) {
|
||||
background: linear-gradient(180deg, #e6fffc 0%, #ffffff 30%);
|
||||
}
|
||||
:deep(.teacher_detail) {
|
||||
background: linear-gradient(180deg, #e6efff 0%, #ffffff 30%);
|
||||
}
|
||||
</style>
|
||||
160
src/view/login/index.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="login flex justify-center items-center">
|
||||
<div class="w-[700px] p-[20px] rounded-[10px] bg-white">
|
||||
<div class="web_logo"></div>
|
||||
<Form class="p-4 enter-x" :model="formData" ref="formRef">
|
||||
<!-- <FormItem name="tenantId" class="enter-x" v-if="tenant.tenantEnabled">
|
||||
<Select v-model:value="formData.tenantId" size="large" @change="handleTenantChange">
|
||||
<template #suffixIcon>
|
||||
<Icon icon="mdi:company" />
|
||||
</template>
|
||||
<SelectOption v-for="item in tenant.voList" :key="item.tenantId" :value="item.tenantId">{{
|
||||
item.companyName
|
||||
}}</SelectOption>
|
||||
</Select>
|
||||
</FormItem> -->
|
||||
|
||||
<FormItem name="teacherId" class="enter-x">
|
||||
<Input
|
||||
size="large"
|
||||
v-model:value="formData.teacherId"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem name="password" class="enter-x">
|
||||
<InputPassword
|
||||
size="large"
|
||||
visibilityToggle
|
||||
v-model:value="formData.password"
|
||||
placeholder="请输入"
|
||||
@keypress.enter="handleLogin"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- <FormItem name="code" class="enter-x" v-if="image.requiredCaptcha">
|
||||
<Input
|
||||
ref="imageCodeRef"
|
||||
size="large"
|
||||
v-model:value="formData.code"
|
||||
placeholder="输入验证码"
|
||||
@keypress.enter="handleLogin"
|
||||
>
|
||||
<template #addonAfter>
|
||||
<Image
|
||||
class="rounded-r-lg"
|
||||
:preview="false"
|
||||
:height="40"
|
||||
:width="105"
|
||||
:src="image.imageInfo"
|
||||
@click="refreshCaptchaImage"
|
||||
/>
|
||||
</template>
|
||||
</Input>
|
||||
</FormItem> -->
|
||||
|
||||
<FormItem class="enter-x mt-15">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
block
|
||||
@click="handleLogin"
|
||||
:loading="loading"
|
||||
>
|
||||
登录/注册
|
||||
</Button>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Checkbox v-model:checked="isAgree" size="small" />
|
||||
<span class="text-xs text-[#808080] pl-2"
|
||||
>我已阅读并同意《用户协议》和
|
||||
《隐私政策》未注册的邮箱将自动创建账号</span
|
||||
>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import { reactive, ref, unref, computed, onMounted } from "vue";
|
||||
import { loginAPI, getTeacherListApi, getUserInfoApi } from "../../request/api";
|
||||
import { useUserStore } from "../../store/user.ts";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
let userStore: any = useUserStore();
|
||||
console.log("userStore", userStore);
|
||||
|
||||
import {
|
||||
Checkbox,
|
||||
Form,
|
||||
Input,
|
||||
Row,
|
||||
Col,
|
||||
Button,
|
||||
Image,
|
||||
Select,
|
||||
SelectOption,
|
||||
Divider,
|
||||
} from "ant-design-vue";
|
||||
|
||||
const isAgree = ref(true);
|
||||
const router = useRouter();
|
||||
|
||||
const ACol = Col;
|
||||
const ARow = Row;
|
||||
const FormItem = Form.Item;
|
||||
const InputPassword = Input.Password;
|
||||
// const { notification, createErrorModal } = useMessage();
|
||||
// const { prefixCls } = useDesign("login");
|
||||
// const userStore = useUserStore();
|
||||
|
||||
// const { setLoginState, getLoginState } = useLoginState();
|
||||
// const { getFormRules } = useFormRules();
|
||||
|
||||
const formRef = ref();
|
||||
const loading = ref(false);
|
||||
const formData: any = reactive({
|
||||
teacherId: "995042",
|
||||
password: "123456",
|
||||
});
|
||||
async function handleLogin() {
|
||||
// let res1 = await getTeacherListApi();
|
||||
// console.log("res1", res1);
|
||||
|
||||
let res = await loginAPI(formData);
|
||||
if (res.code == 200) {
|
||||
ElMessage.success(`登录成功!`);
|
||||
|
||||
userStore.SetUserInfo({ data: { auth: res.data } });
|
||||
let userInfo = await getUserInfoApi();
|
||||
userInfo.data.auth = res.data;
|
||||
userStore.SetUserInfo(userInfo);
|
||||
console.log("userStore.userInfo111", userStore.userInfo);
|
||||
|
||||
router.push("/");
|
||||
}
|
||||
console.log("***", res);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.login {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: url("../../assets/web/banner.png"), rgb(255 255 255 / 100%);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
.web_logo {
|
||||
width: 250px;
|
||||
height: 40px;
|
||||
margin: 10px 16px;
|
||||
background: url("../../assets/web/weblogo1.png"), rgb(255 255 255 / 100%);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
:deep(button) {
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
67
src/view/statistic/BarChart.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<!-- 图表容器:设置宽度和高度 -->
|
||||
<div ref="chartRef" style="width: 600px; height: 400px"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 引入 Vue 的 Composition API
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
// 引入 echarts 库
|
||||
import * as echarts from "echarts";
|
||||
|
||||
// 获取 DOM 元素的引用(用于初始化图表)
|
||||
const chartRef = ref(null);
|
||||
// 存储 ECharts 实例
|
||||
let chartInstance = null;
|
||||
|
||||
// 初始化图表的方法
|
||||
const initChart = () => {
|
||||
if (chartRef.value) {
|
||||
// 初始化 echarts 实例
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
// 配置项
|
||||
const option = {
|
||||
title: {
|
||||
text: "", // 图表标题
|
||||
},
|
||||
tooltip: {}, // 默认提示框配置
|
||||
legend: {
|
||||
data: ["升学率"], // 图例名称
|
||||
},
|
||||
xAxis: {
|
||||
data: ["2020", "2021", "2022", "2023", "2024", "2025"], // X 轴数据
|
||||
},
|
||||
yAxis: {}, // Y 轴默认配置
|
||||
series: [
|
||||
{
|
||||
name: "升学率", // 系列名称
|
||||
type: "bar", // 图表类型为柱状图
|
||||
data: [40, 42, 42, 43, 43, 45], // 数据值
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 使用配置项渲染图表
|
||||
chartInstance.setOption(option);
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小变化时调整图表尺寸
|
||||
const resizeChart = () => {
|
||||
chartInstance?.resize();
|
||||
};
|
||||
|
||||
// 组件挂载后执行初始化
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
// 监听窗口大小变化事件以支持响应式
|
||||
window.addEventListener("resize", resizeChart);
|
||||
});
|
||||
|
||||
// 组件卸载前清理资源,防止内存泄漏
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", resizeChart);
|
||||
chartInstance?.dispose(); // 销毁 echarts 实例
|
||||
});
|
||||
</script>
|
||||
67
src/view/statistic/BarChart2.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<!-- 图表容器:设置宽度和高度 -->
|
||||
<div ref="chartRef" style="width: 600px; height: 400px"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 引入 Vue 的 Composition API
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
// 引入 echarts 库
|
||||
import * as echarts from "echarts";
|
||||
|
||||
// 获取 DOM 元素的引用(用于初始化图表)
|
||||
const chartRef = ref(null);
|
||||
// 存储 ECharts 实例
|
||||
let chartInstance = null;
|
||||
|
||||
// 初始化图表的方法
|
||||
const initChart = () => {
|
||||
if (chartRef.value) {
|
||||
// 初始化 echarts 实例
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
// 配置项
|
||||
const option = {
|
||||
title: {
|
||||
text: "", // 图表标题
|
||||
},
|
||||
tooltip: {}, // 默认提示框配置
|
||||
legend: {
|
||||
data: ["留琼率"], // 图例名称
|
||||
},
|
||||
xAxis: {
|
||||
data: ["2020", "2021", "2022", "2023", "2024", "2025"], // X 轴数据
|
||||
},
|
||||
yAxis: {}, // Y 轴默认配置
|
||||
series: [
|
||||
{
|
||||
name: "留琼率", // 系列名称
|
||||
type: "bar", // 图表类型为柱状图
|
||||
data: [50, 52, 52, 53, 53, 56], // 数据值
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 使用配置项渲染图表
|
||||
chartInstance.setOption(option);
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小变化时调整图表尺寸
|
||||
const resizeChart = () => {
|
||||
chartInstance?.resize();
|
||||
};
|
||||
|
||||
// 组件挂载后执行初始化
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
// 监听窗口大小变化事件以支持响应式
|
||||
window.addEventListener("resize", resizeChart);
|
||||
});
|
||||
|
||||
// 组件卸载前清理资源,防止内存泄漏
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", resizeChart);
|
||||
chartInstance?.dispose(); // 销毁 echarts 实例
|
||||
});
|
||||
</script>
|
||||
328
src/view/statistic/BarLine.vue
Normal file
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<!-- 图表容器:设置宽度和高度 -->
|
||||
<div
|
||||
ref="chartRef"
|
||||
style="width: 800px; height: 400px; border-radius: 10px"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 引入 Vue 的 Composition API
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
// 引入 echarts 库
|
||||
import * as echarts from "echarts";
|
||||
|
||||
// 获取 DOM 元素的引用(用于初始化图表)
|
||||
const chartRef = ref(null);
|
||||
// 存储 ECharts 实例
|
||||
let chartInstance = null;
|
||||
|
||||
const dataColor = ["#4EB2FE", "#f19051", "#F47505", "#20d450"];
|
||||
const data1 = [84, 85, 86, 87, 89, 90];
|
||||
const data2 = [84, 85, 86, 87, 89, 90];
|
||||
const nameArr = ["2020", "2021", "2022", "2023", "2024", "2025"];
|
||||
const axiscolor = ["#dde1e8", "#222222"];
|
||||
const colorArr1 = ["#f66c6b", "#fb8484", "#ff9d9d"];
|
||||
const colorArr2 = ["#1aa4f2", "#4eb6f9", "#88cbfe"];
|
||||
const color1 = {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
x2: 0,
|
||||
y: 1,
|
||||
y2: 0,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: colorArr1[0],
|
||||
},
|
||||
// {
|
||||
// offset: 0.5,
|
||||
// color: colorArr1[0]
|
||||
// },
|
||||
{
|
||||
offset: 0.5,
|
||||
color: colorArr1[1],
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: colorArr1[1],
|
||||
},
|
||||
],
|
||||
};
|
||||
const color2 = {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
x2: 0,
|
||||
y: 1,
|
||||
y2: 0,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: colorArr2[0],
|
||||
},
|
||||
// {
|
||||
// offset: 0.5,
|
||||
// color: colorArr2[0]
|
||||
// },
|
||||
{
|
||||
offset: 0.5,
|
||||
color: colorArr2[1],
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: colorArr2[1],
|
||||
},
|
||||
],
|
||||
};
|
||||
const barWidth = 18;
|
||||
|
||||
// 初始化图表的方法
|
||||
const initChart = () => {
|
||||
if (chartRef.value) {
|
||||
// 初始化 echarts 实例
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
// 配置项
|
||||
const option = {
|
||||
backgroundColor: "#fff",
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
textStyle: {
|
||||
fontSize: "100%",
|
||||
},
|
||||
},
|
||||
legend: [
|
||||
{
|
||||
data: ["就业率", "升学率"],
|
||||
// textStyle: {
|
||||
// color: '#9FC6F6'
|
||||
// },
|
||||
top: "1%",
|
||||
left: "200",
|
||||
icon: "rect", // 形状
|
||||
itemWidth: 10, // 宽
|
||||
itemHeight: 10, // 高
|
||||
},
|
||||
{
|
||||
data: ["留琼率"],
|
||||
// textStyle: {
|
||||
// color: '#9FC6F6'
|
||||
// },
|
||||
top: "1%",
|
||||
left: "330",
|
||||
icon: "rect",
|
||||
itemWidth: 10,
|
||||
itemHeight: 3,
|
||||
},
|
||||
],
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: axiscolor[1],
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
fontSize: 12,
|
||||
// rotate: 15,
|
||||
color: axiscolor[1],
|
||||
},
|
||||
data: nameArr,
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
// name: '单位:万户',
|
||||
nameTextStyle: {
|
||||
fontFamily: "ShiShangZhongHeiJianTi",
|
||||
fontSize: 20,
|
||||
color: "#ffd200",
|
||||
align: "center",
|
||||
padding: [0, 0, 10, 0],
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: axiscolor[0],
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "#dde1e8",
|
||||
type: "dashed",
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
fontFamily: "Dinpro",
|
||||
fontSize: 14,
|
||||
color: axiscolor[1],
|
||||
},
|
||||
},
|
||||
{
|
||||
// name: '单位:亿千伏安',
|
||||
nameTextStyle: {
|
||||
fontFamily: "ShiShangZhongHeiJianTi",
|
||||
fontSize: 20,
|
||||
color: "#ffd200",
|
||||
align: "center",
|
||||
padding: [0, 50, 10, 0],
|
||||
},
|
||||
type: "value",
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: axiscolor[0],
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: axiscolor[1],
|
||||
fontFamily: "Dinpro",
|
||||
fontSize: 10,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "#dde1e8",
|
||||
type: "dashed",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
z: 1,
|
||||
name: "升学率",
|
||||
type: "bar",
|
||||
barWidth: barWidth,
|
||||
barGap: "0%",
|
||||
data: data1,
|
||||
itemStyle: {
|
||||
color: color1,
|
||||
},
|
||||
},
|
||||
{
|
||||
z: 3,
|
||||
name: "蓝色",
|
||||
type: "pictorialBar",
|
||||
symbolPosition: "end",
|
||||
data: data1,
|
||||
symbol: "circle",
|
||||
symbolOffset: ["-62%", "-65%"],
|
||||
symbolSize: [18, 8],
|
||||
itemStyle: {
|
||||
borderWidth: 2,
|
||||
color: colorArr1[2],
|
||||
},
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
z: 1,
|
||||
name: "就业率",
|
||||
type: "bar",
|
||||
barWidth: barWidth,
|
||||
barGap: "20%",
|
||||
data: data2,
|
||||
itemStyle: {
|
||||
color: color2,
|
||||
},
|
||||
},
|
||||
{
|
||||
z: 3,
|
||||
name: "橙色",
|
||||
type: "pictorialBar",
|
||||
symbolPosition: "end",
|
||||
data: data2,
|
||||
symbol: "circle",
|
||||
symbolOffset: ["60%", "-60%"],
|
||||
symbolSize: [18, 8],
|
||||
itemStyle: {
|
||||
borderWidth: 2,
|
||||
color: colorArr2[2],
|
||||
},
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "升学率占比",
|
||||
// type: "line",
|
||||
// smooth: true,
|
||||
// symbolSize: 1,
|
||||
// yAxisIndex: 1,
|
||||
// itemStyle: {
|
||||
// color: dataColor[2],
|
||||
// },
|
||||
// lineStyle: {
|
||||
// width: 2,
|
||||
// color: dataColor[2],
|
||||
// },
|
||||
// // animationDuration: 5800,
|
||||
// // animationEasing: 'quadraticOut',
|
||||
// showSymbol: true,
|
||||
// data: [4, 12, 4, 12, 8, 11, 9, 6, 8, 5, 6, 7, 15],
|
||||
// },
|
||||
{
|
||||
name: "留琼率",
|
||||
type: "line",
|
||||
smooth: true,
|
||||
symbolSize: 1,
|
||||
yAxisIndex: 1,
|
||||
itemStyle: {
|
||||
color: dataColor[3],
|
||||
},
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: dataColor[3],
|
||||
},
|
||||
// animationDuration: 5800,
|
||||
// animationEasing: 'quadraticOut',
|
||||
showSymbol: true,
|
||||
data: [60, 62, 64, 66, 68, 70],
|
||||
},
|
||||
],
|
||||
grid: {
|
||||
top: "10%",
|
||||
left: "10%",
|
||||
right: "8%",
|
||||
bottom: "15%",
|
||||
},
|
||||
};
|
||||
|
||||
// 使用配置项渲染图表
|
||||
chartInstance.setOption(option);
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小变化时调整图表尺寸
|
||||
const resizeChart = () => {
|
||||
chartInstance?.resize();
|
||||
};
|
||||
|
||||
// 组件挂载后执行初始化
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
// 监听窗口大小变化事件以支持响应式
|
||||
window.addEventListener("resize", resizeChart);
|
||||
});
|
||||
|
||||
// 组件卸载前清理资源,防止内存泄漏
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", resizeChart);
|
||||
chartInstance?.dispose(); // 销毁 echarts 实例
|
||||
});
|
||||
</script>
|
||||
67
src/view/statistic/LineChart.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<!-- 图表容器:设置宽度和高度 -->
|
||||
<div ref="chartRef" style="width: 600px; height: 400px"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 引入 Vue 的 Composition API
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
// 引入 echarts 库
|
||||
import * as echarts from "echarts";
|
||||
|
||||
// 获取 DOM 元素的引用(用于初始化图表)
|
||||
const chartRef = ref(null);
|
||||
// 存储 ECharts 实例
|
||||
let chartInstance = null;
|
||||
|
||||
// 初始化图表的方法
|
||||
const initChart = () => {
|
||||
if (chartRef.value) {
|
||||
// 初始化 echarts 实例
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
// 配置项
|
||||
const option = {
|
||||
title: {
|
||||
text: "", // 图表标题
|
||||
},
|
||||
tooltip: {}, // 默认提示框配置
|
||||
legend: {
|
||||
data: ["就业率"], // 图例名称
|
||||
},
|
||||
xAxis: {
|
||||
data: ["2020", "2021", "2022", "2023", "2024", "2025"], // X 轴数据
|
||||
},
|
||||
yAxis: {}, // Y 轴默认配置
|
||||
series: [
|
||||
{
|
||||
name: "就业率", // 系列名称
|
||||
type: "line", // 图表类型为柱状图
|
||||
data: [96, 96, 97, 97, 99, 99], // 数据值
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 使用配置项渲染图表
|
||||
chartInstance.setOption(option);
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小变化时调整图表尺寸
|
||||
const resizeChart = () => {
|
||||
chartInstance?.resize();
|
||||
};
|
||||
|
||||
// 组件挂载后执行初始化
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
// 监听窗口大小变化事件以支持响应式
|
||||
window.addEventListener("resize", resizeChart);
|
||||
});
|
||||
|
||||
// 组件卸载前清理资源,防止内存泄漏
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", resizeChart);
|
||||
chartInstance?.dispose(); // 销毁 echarts 实例
|
||||
});
|
||||
</script>
|
||||
85
src/view/statistic/index.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="sta_bg p-[20px] mt-[96px] relative">
|
||||
<div class="absolute left-[250px] top-[-65px]">
|
||||
<div class="mr-[20px]">
|
||||
<!-- <Button type="primary" size="large" block @click="back"> 返回 </Button> -->
|
||||
<div
|
||||
@click="back"
|
||||
class="cursor-pointer px-[20px] py-[6px] rounded-[6px] bg-[#F5F6FA]"
|
||||
>
|
||||
< 返回
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-[calc(100%-132px)]">
|
||||
<el-scrollbar>
|
||||
<div class="flex">
|
||||
<div class="mr-[20px]">
|
||||
<div class="relative top-[36px] left-[90px] z-[1]">
|
||||
电子信息工程系
|
||||
</div>
|
||||
<div class="flex rounded-[10px] p-[10px] bg-white">
|
||||
<BarLine></BarLine>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="relative top-[36px] left-[90px] z-[1]">通信工程系</div>
|
||||
<div class="flex rounded-[10px] p-[10px] bg-white">
|
||||
<BarLine></BarLine>
|
||||
|
||||
<!-- <BarChart :data="{}"></BarChart>
|
||||
<LineChart :data="{}"></LineChart>
|
||||
<PieChart :data="{}"></PieChart> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div>
|
||||
<div class="relative top-[36px] left-[90px] z-[1]">人工智能系</div>
|
||||
<div class="flex rounded-[10px] p-[10px] bg-white">
|
||||
<BarLine></BarLine>
|
||||
|
||||
<!-- <BarChart :data="{}"></BarChart>
|
||||
<LineChart :data="{}"></LineChart>
|
||||
<PieChart :data="{}"></PieChart> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div>
|
||||
<div class="text-center">本科实验教学中心</div>
|
||||
<div class="flex">
|
||||
<BarLine></BarLine>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BarChart from "./BarChart.vue";
|
||||
import LineChart from "./LineChart.vue";
|
||||
import PieChart from "./BarChart2.vue";
|
||||
import BarLine from "./BarLine.vue";
|
||||
import { Button } from "ant-design-vue";
|
||||
const router = useRouter();
|
||||
|
||||
function back() {
|
||||
router.go(-1);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(button) {
|
||||
outline: none;
|
||||
}
|
||||
.sta_bg {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
/* background-color: #f1f6fe; */
|
||||
// background: url("../../assets/web/digimg.png"), rgb(255 255 255 / 100%);
|
||||
// background-repeat: no-repeat;
|
||||
// background-position: center;
|
||||
// background-size: cover;
|
||||
}
|
||||
</style>
|
||||
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
8
tailwind.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
15
tsconfig.app.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
23
tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
],
|
||||
"include": ["env.d.ts"]
|
||||
}
|
||||
|
||||
// {
|
||||
// "files": [],
|
||||
// "references": [
|
||||
// { "path": "./tsconfig.app.json" },
|
||||
// { "path": "./tsconfig.node.json" }
|
||||
// ],
|
||||
// "include": ["env.d.ts"]
|
||||
// }
|
||||
25
tsconfig.node.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
1
tsconfig.tsbuildinfo
Normal file
@@ -0,0 +1 @@
|
||||
{"root":["./env.d.ts"],"errors":true,"version":"5.8.3"}
|
||||
55
vite.config.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { resolve } from "path";
|
||||
import path from "path";
|
||||
// 自动导入vue中hook reactive ref等
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
//自动导入ui-组件 比如说ant-design-vue element-plus等
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
//安装两行后你会发现在组件中不用再导入ref,reactive等
|
||||
imports: ["vue", "vue-router"],
|
||||
//存放的位置
|
||||
dts: "src/auto-import.d.ts",
|
||||
}),
|
||||
Components({
|
||||
// 引入组件的,包括自定义组件
|
||||
// 存放的位置
|
||||
dts: "src/components.d.ts",
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
"/basic-api": {
|
||||
// target: 'http://api.holo-land.com/',
|
||||
// target: 'http://lh.holo.huatengkexun.com/',
|
||||
// target: "http://chen.hnedu.huatengkexun.com/",
|
||||
// target: "https://hnaicm.admin.huatengkexun.com/",
|
||||
target: " http://localhost:6669/",
|
||||
|
||||
changeOrigin: true,
|
||||
// ws: true,
|
||||
rewrite: (path) => path.replace(/^\/basic-api/, ""),
|
||||
},
|
||||
"/ai-basic-api": {
|
||||
target: "https://hn.dify.holo-land.com/",
|
||||
changeOrigin: true,
|
||||
// ws: true,
|
||||
rewrite: (path) => path.replace(/^\/ai-basic-api/, ""),
|
||||
},
|
||||
},
|
||||
open: true, // 项目启动后,自动打开
|
||||
// warmup: {
|
||||
// clientFiles: ["./index.html", "./src/{views,components}/*"],
|
||||
// },
|
||||
},
|
||||
});
|
||||