This commit is contained in:
汤凯
2025-10-19 14:29:36 +08:00
parent b7570e392c
commit ae57bd1948
45 changed files with 6701 additions and 0 deletions

24
.gitignore vendored Normal file
View 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
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

8
env.d.ts vendored Normal file
View 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

Binary file not shown.

13
index.html Normal file
View 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

File diff suppressed because it is too large Load Diff

38
package.json Normal file
View 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
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

1
public/vite.svg Normal file
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
src/assets/web/avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

78
src/auto-import.d.ts vendored Normal file
View 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
View 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
View 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>

View 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>

View 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
View 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
View 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; //返回后端自定义的200404500这种状态码
}
// 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
View 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
View 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
View 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
View 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
View 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
View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

8
tailwind.config.js Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
{"root":["./env.d.ts"],"errors":true,"version":"5.8.3"}

55
vite.config.ts Normal file
View 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({
//安装两行后你会发现在组件中不用再导入refreactive等
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}/*"],
// },
},
});