commit 0a6fff3cf2e8bd23613ba08b4791753dc4c70ea5
Author: yans <498418533@qq.com>
Date: Mon Nov 24 14:49:39 2025 +0800
第一次提交
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..534a03f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+### 环境配置
+
+node版本: 22.10.0
+pnpm版本: 6.11.0
+
+### 安装依赖
+
+pnpm install
+
+### 启动项目
+
+pnpm run dev
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..3a0bd42
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ 雷达⽬标识别迁移部署系统
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..a71421e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,64 @@
+{
+ "name": "radar-system",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --open ",
+ "build": "vite build",
+ "preview": "vite preview",
+ "test:unit": "vitest",
+ "build-only": "vite build",
+ "lint": "eslint . --fix",
+ "format": "prettier --write src/"
+ },
+ "dependencies": {
+ "@types/d3": "^7.4.3",
+ "ant-design-vue": "4.x",
+ "d3": "^7.9.0",
+ "echarts": "^5.6.0",
+ "lodash": "^4.17.21",
+ "pinia": "^3.0.1",
+ "screenfull": "^5.1.0",
+ "socket.io-client": "^4.2.0",
+ "streamsaver": "^2.0.6",
+ "swiper": "^12.0.3",
+ "vue": "^3.5.13",
+ "vue-router": "^4.5.0"
+ },
+ "devDependencies": {
+ "@ant-design/icons-vue": "^7.0.1",
+ "@tsconfig/node22": "^22.0.1",
+ "@types/jsdom": "^21.1.7",
+ "@types/node": "^22.14.0",
+ "@vitejs/plugin-vue": "^5.2.3",
+ "@vitejs/plugin-vue-jsx": "^4.1.2",
+ "@vitest/eslint-plugin": "^1.1.39",
+ "@vue/eslint-config-prettier": "^10.2.0",
+ "@vue/eslint-config-typescript": "^14.5.0",
+ "@vue/test-utils": "^2.4.6",
+ "@vue/tsconfig": "^0.7.0",
+ "autoprefixer": "^10.4.19",
+ "dayjs": "^1.11.13",
+ "eslint": "^9.22.0",
+ "eslint-plugin-vue": "~10.0.0",
+ "jiti": "^2.4.2",
+ "jsdom": "^26.0.0",
+ "npm-run-all2": "^7.0.2",
+ "postcss-normalize-charset": "^6.0.1",
+ "postcss-preset-env": "^9.3.0",
+ "prettier": "3.5.3",
+ "sass": "^1.66.1",
+ "sass-loader": "^13.3.2",
+ "typescript": "~5.8.0",
+ "vite": "^6.2.4",
+ "vite-plugin-vue-devtools": "^7.7.2",
+ "vitest": "^3.1.1",
+ "vue-tsc": "^2.2.8"
+ },
+ "packageManager": "pnpm@6.11.0",
+ "engines": {
+ "node": ">=18.18.2 <= 22.10.0",
+ "pnpm": ">=6.11.0"
+ }
+}
diff --git a/public/css/tinymceEdit--wirte.css b/public/css/tinymceEdit--wirte.css
new file mode 100644
index 0000000..48aec65
--- /dev/null
+++ b/public/css/tinymceEdit--wirte.css
@@ -0,0 +1,18 @@
+@font-face {
+ font-family: '黑体';
+ src: url('/static/font/MyFont.ttf');
+}
+
+@font-face {
+ font-family: 'Times New Roman';
+ src: url('/static/font/Times-New-Romance-1.ttf');
+}
+
+@font-face {
+ font-family: '宋体';
+ src: url('/static/font/ZiTiGuanJiaFangSongTi-2.ttf');
+}
+
+* {
+ line-height: 1.5;
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..46c2933
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..986629a
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 0000000..46c2933
Binary files /dev/null and b/src/assets/logo.png differ
diff --git a/src/components/Base/Layout/LoginLayout/index.vue b/src/components/Base/Layout/LoginLayout/index.vue
new file mode 100644
index 0000000..c282c51
--- /dev/null
+++ b/src/components/Base/Layout/LoginLayout/index.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Base/Layout/SystemLayout/Content/index.vue b/src/components/Base/Layout/SystemLayout/Content/index.vue
new file mode 100644
index 0000000..21bf243
--- /dev/null
+++ b/src/components/Base/Layout/SystemLayout/Content/index.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Base/Layout/SystemLayout/Header/index.vue b/src/components/Base/Layout/SystemLayout/Header/index.vue
new file mode 100644
index 0000000..7458f03
--- /dev/null
+++ b/src/components/Base/Layout/SystemLayout/Header/index.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Base/Layout/SystemLayout/Sider/hooks/router.ts b/src/components/Base/Layout/SystemLayout/Sider/hooks/router.ts
new file mode 100644
index 0000000..b00b489
--- /dev/null
+++ b/src/components/Base/Layout/SystemLayout/Sider/hooks/router.ts
@@ -0,0 +1,44 @@
+import { h } from 'vue'
+import Icon from '@/components/Module/Icon/index.vue'
+
+
+export const useMain = () => {
+ // 路由过滤,只显示要显示的路由
+ const routerFilter = (menus: Array>) => {
+ return menus.filter((item: Record) => {
+ if (item.children) {
+ item.children = routerFilter(item.children)
+ }
+
+ return !item.meta.hidden
+ })
+ }
+
+ // 路由格式化
+ const routerFormat = (arr: Array>) => {
+ return arr.map((item: Record) => {
+ if (item.children && item.children.length) {
+ item.children = routerFormat(item.children)
+ }
+ return {
+ meta: item.meta,
+ icon: () =>
+ h(Icon, {
+ name: item.meta.icon,
+ }),
+ fullPath: item.fullPath,
+ pid: item.parentId,
+ key: item.name,
+ label: item.meta.title || '',
+ title: item.meta.title || '',
+ children: item.children?.length ? item.children : '',
+ type: item.type || null,
+ }
+ })
+ }
+
+ return {
+ routerFilter,
+ routerFormat
+ }
+}
diff --git a/src/components/Base/Layout/SystemLayout/Sider/index.vue b/src/components/Base/Layout/SystemLayout/Sider/index.vue
new file mode 100644
index 0000000..86e2185
--- /dev/null
+++ b/src/components/Base/Layout/SystemLayout/Sider/index.vue
@@ -0,0 +1,240 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Base/Layout/SystemLayout/index.vue b/src/components/Base/Layout/SystemLayout/index.vue
new file mode 100644
index 0000000..5d69aab
--- /dev/null
+++ b/src/components/Base/Layout/SystemLayout/index.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Base/Layout/ViewLayout/index.vue b/src/components/Base/Layout/ViewLayout/index.vue
new file mode 100644
index 0000000..3084ee8
--- /dev/null
+++ b/src/components/Base/Layout/ViewLayout/index.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/src/components/Base/Tools/Info/index.vue b/src/components/Base/Tools/Info/index.vue
new file mode 100644
index 0000000..7bf4ae7
--- /dev/null
+++ b/src/components/Base/Tools/Info/index.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
![]()
+
+
+ {{ info.title }}
+
+
+
+
+
diff --git a/src/components/Base/Tools/Menus/index.vue b/src/components/Base/Tools/Menus/index.vue
new file mode 100644
index 0000000..9d82de0
--- /dev/null
+++ b/src/components/Base/Tools/Menus/index.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Base/Tools/UserBox/hooks/index.ts b/src/components/Base/Tools/UserBox/hooks/index.ts
new file mode 100644
index 0000000..de4dc6d
--- /dev/null
+++ b/src/components/Base/Tools/UserBox/hooks/index.ts
@@ -0,0 +1,36 @@
+export const useMain = () => {
+ const menus = [
+ {
+ name: '设置',
+ path:'/toolsSet',
+ key: 'toolsSet',
+ icon: 'briefCase'
+ },
+ {
+ name: '已归档对话',
+ path:'/workspace/knowledge',
+ key: 'knowledge',
+ icon: 'book'
+ },
+ {
+ name: '管理员面板',
+ path:'/fileManagementSet',
+ key: 'fileManagementSet',
+ icon: 'file'
+ },
+ ]
+
+ const operateMenus = [
+ {
+ name: '退出登录',
+ path:'/login',
+ key: 'login',
+ icon: 'briefCase'
+ }
+ ]
+
+ return {
+ menus,
+ operateMenus
+ }
+}
diff --git a/src/components/Base/Tools/UserBox/index.vue b/src/components/Base/Tools/UserBox/index.vue
new file mode 100644
index 0000000..f09d5c4
--- /dev/null
+++ b/src/components/Base/Tools/UserBox/index.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Module/AsyncContent/ErrorLoading.vue b/src/components/Module/AsyncContent/ErrorLoading.vue
new file mode 100644
index 0000000..5bf6e0f
--- /dev/null
+++ b/src/components/Module/AsyncContent/ErrorLoading.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Module/AsyncContent/index.vue b/src/components/Module/AsyncContent/index.vue
new file mode 100644
index 0000000..8693f4b
--- /dev/null
+++ b/src/components/Module/AsyncContent/index.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Module/Icon/components/SvgIcon.vue b/src/components/Module/Icon/components/SvgIcon.vue
new file mode 100644
index 0000000..fb45c66
--- /dev/null
+++ b/src/components/Module/Icon/components/SvgIcon.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
diff --git a/src/components/Module/Icon/components/icon.ts b/src/components/Module/Icon/components/icon.ts
new file mode 100644
index 0000000..db1dde4
--- /dev/null
+++ b/src/components/Module/Icon/components/icon.ts
@@ -0,0 +1,92 @@
+// Icon Store - For efficient SVG icon imports (Vue3 Composition API)
+import { ref } from 'vue';
+
+type IconMap = {
+ [key: string]: string;
+}
+
+// 响应式状态
+const availableIcons = ref({});
+const iconContents = ref({});
+const iconCache = new Map();
+
+// 使用Vite的import.meta.glob动态导入
+export const initialize = async () => {
+ const iconModules = import.meta.glob('/src/assets/icons/*.svg', {
+ eager: true,
+ as: 'raw'
+ });
+
+ Object.entries(iconModules).forEach(([path, content]: [string, string]) => {
+ const name = path?.split('/')?.pop()?.replace('.svg', '') as string;
+ iconContents.value[name] = content;
+ availableIcons.value[name] = `data:image/svg+xml;base64,${btoa(content)}`;
+ });
+
+ return Object.keys(availableIcons).length;
+};
+
+// 获取图标URL
+export const getIconUrl = (name: string) => {
+ if (iconCache.has(name)) return iconCache.get(name);
+
+ const url = availableIcons.value[name];
+ if (url) iconCache.set(name, url);
+ return url;
+};
+
+// 获取原始SVG内容
+export const getIconContent = (name: string) => {
+ return iconContents.value[name] || null;
+};
+
+initialize()
+
+// return {
+// availableIcons: ref(availableIcons),
+// iconContents: ref(iconContents),
+// initialize,
+// getIconUrl,
+// getIconContent
+// };
+
+// 初始化图标存储
+// export function useIconStore() {
+// // 使用Vite的import.meta.glob动态导入
+// const initialize = async () => {
+// const iconModules = import.meta.glob('/src/lib/assets/icons/*.svg', {
+// eager: true,
+// as: 'raw'
+// });
+
+// Object.entries(iconModules).forEach(([path, content]: [string, string]) => {
+// const name = path?.split('/')?.pop()?.replace('.svg', '') as string;
+// iconContents.value[name] = content;
+// availableIcons.value[name] = `data:image/svg+xml;base64,${btoa(content)}`;
+// });
+
+// return Object.keys(availableIcons).length;
+// };
+
+// // 获取图标URL
+// const getIconUrl = (name: string) => {
+// if (iconCache.has(name)) return iconCache.get(name);
+
+// const url = availableIcons.value[name];
+// if (url) iconCache.set(name, url);
+// return url;
+// };
+
+// // 获取原始SVG内容
+// const getIconContent = (name: string) => {
+// return iconContents.value[name] || null;
+// };
+
+// return {
+// availableIcons: ref(availableIcons),
+// iconContents: ref(iconContents),
+// initialize,
+// getIconUrl,
+// getIconContent
+// };
+// }
diff --git a/src/components/Module/Icon/index.vue b/src/components/Module/Icon/index.vue
new file mode 100644
index 0000000..397dcdf
--- /dev/null
+++ b/src/components/Module/Icon/index.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Module/Modal/components/ModalHeader.vue b/src/components/Module/Modal/components/ModalHeader.vue
new file mode 100644
index 0000000..3813b7e
--- /dev/null
+++ b/src/components/Module/Modal/components/ModalHeader.vue
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Module/Modal/index.vue b/src/components/Module/Modal/index.vue
new file mode 100644
index 0000000..1eb79f2
--- /dev/null
+++ b/src/components/Module/Modal/index.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+ {{ props.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{
+ confirmText
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/__tests__/HelloWorld.spec.ts b/src/components/__tests__/HelloWorld.spec.ts
new file mode 100644
index 0000000..2533202
--- /dev/null
+++ b/src/components/__tests__/HelloWorld.spec.ts
@@ -0,0 +1,11 @@
+import { describe, it, expect } from 'vitest'
+
+import { mount } from '@vue/test-utils'
+import HelloWorld from '../HelloWorld.vue'
+
+describe('HelloWorld', () => {
+ it('renders properly', () => {
+ const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
+ expect(wrapper.text()).toContain('Hello Vitest')
+ })
+})
diff --git a/src/config/index.ts b/src/config/index.ts
new file mode 100644
index 0000000..6bd8909
--- /dev/null
+++ b/src/config/index.ts
@@ -0,0 +1,8 @@
+// import logo from '@/assets/images/logo/logo.png'
+// import authBg from '@/assets/images/auth/auth-bg.jpg'
+
+export const info = {
+ logo: '',
+ title: "",
+ bgImg: ''
+}
diff --git a/src/core/lazy_use.ts b/src/core/lazy_use.ts
new file mode 100644
index 0000000..90a4e7e
--- /dev/null
+++ b/src/core/lazy_use.ts
@@ -0,0 +1,115 @@
+import type { App } from 'vue'
+import {
+ App as antApp,
+ Button,
+ Tree,
+ ConfigProvider,
+ Layout,
+ Menu,
+ PageHeader,
+ Dropdown,
+ Drawer,
+ Space,
+ Switch,
+ Input,
+ Form,
+ Popover,
+ Row,
+ Col,
+ Select,
+ Checkbox,
+ Modal,
+ Tag,
+ AutoComplete,
+ Upload,
+ Radio,
+ Tabs,
+ Timeline,
+ Carousel,
+ Spin,
+ List,
+ Tooltip,
+ Card,
+ DatePicker,
+ Divider,
+ message,
+ InputNumber,
+ TreeSelect,
+ Steps,
+ Pagination,
+ Popconfirm,
+ Cascader,
+ Badge,
+ Empty,
+ Progress,
+ Affix,
+ Descriptions,
+ Slider,
+ Table
+} from 'ant-design-vue'
+
+import { router } from '@/router/index.ts'
+
+
+const [ messageApi, contextHolder] = message.useMessage();
+
+// 注册全局组件
+export function loadComponent(app: App) {
+ app
+ .use(ConfigProvider)
+ .use(Layout)
+ .use(Checkbox)
+ .use(Menu)
+ .use(Button)
+ .use(Tree)
+ .use(antApp)
+ .use(PageHeader)
+ .use(Dropdown)
+ .use(Drawer)
+ .use(Space)
+ .use(Switch)
+ .use(Input)
+ .use(Form)
+ .use(Popover)
+ .use(Row)
+ .use(Col)
+ .use(Select)
+ .use(Table)
+ .use(Modal)
+ .use(Tag)
+ .use(AutoComplete)
+ .use(Upload)
+ .use(Radio)
+ .use(Tabs)
+ .use(Timeline)
+ .use(Carousel)
+ .use(Spin)
+ .use(List)
+ .use(Tooltip)
+ .use(Card)
+ .use(DatePicker)
+ .use(Divider)
+ .use(List)
+ .use(InputNumber)
+ .use(TreeSelect)
+ // .use(hevueImgPreview)
+ .use(Steps)
+ .use(Pagination)
+ .use(Popconfirm)
+ .use(Cascader)
+ .use(Badge)
+ .use(Empty)
+ .use(Progress)
+ .use(Affix)
+ .use(Descriptions)
+ .use(Slider)
+
+
+}
+
+// 注册全局方法
+export function loadFunc (app: App) {
+
+
+}
+
diff --git a/src/directive/index.ts b/src/directive/index.ts
new file mode 100644
index 0000000..c3043ae
--- /dev/null
+++ b/src/directive/index.ts
@@ -0,0 +1,8 @@
+import type { App } from 'vue'
+
+
+function directive(app: App) {
+ // 注册指令
+}
+
+export default directive;
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..88e34a7
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,41 @@
+// import './assets/main.css'
+import '@/styles/index.css'
+import 'ant-design-vue/dist/reset.css'
+
+import { createApp } from 'vue'
+import { setupStore } from '@/stores'
+import { router, setupRouter } from '@/router'
+import { setRouteGuard } from '@/router/guard/index'
+import { loadComponent, loadFunc } from '@/core/lazy_use'
+import 'swiper/css';
+import 'swiper/css/navigation';
+import 'swiper/css/pagination';
+import 'swiper/css/effect-fade';
+import 'swiper/css/autoplay';
+
+import * as Icons from '@ant-design/icons-vue'
+
+import App from './App.vue'
+// import router from './router'
+
+
+
+const app = createApp(App)
+
+loadComponent(app)
+
+loadFunc(app)
+
+setupStore(app)
+
+setupRouter(app)
+
+setRouteGuard(router)
+
+const icons:any = Icons
+for (const i in icons) {
+ app.component(i, icons[i])
+}
+
+
+app.mount('#app')
diff --git a/src/router/base/index.ts b/src/router/base/index.ts
new file mode 100644
index 0000000..aa192ad
--- /dev/null
+++ b/src/router/base/index.ts
@@ -0,0 +1,29 @@
+import type { AppRouter } from '@/router/config/index'
+
+export const baseRouter: AppRouter= {
+ name: 'Base',
+ path: '',
+ meta: {
+ title: '首页',
+ hidden: false,
+ hiddenInMenu: true,
+ },
+ component: 'BaseLayout',
+ children: []
+}
+
+export const globalRouter = [
+ {
+ name: 'LoginView',
+ path: '/login',
+ redirect: '/login/index',
+ icon: 'HomeOutlined',
+ hidden: false,
+ meta: {
+ icon: 'HomeOutlined',
+ title: '用户登录',
+ },
+ component: () => import('@/components/Base/Layout/LoginLayout/index.vue'),
+ children: []
+ },
+]
diff --git a/src/router/config/index.ts b/src/router/config/index.ts
new file mode 100644
index 0000000..b90d0ab
--- /dev/null
+++ b/src/router/config/index.ts
@@ -0,0 +1,61 @@
+import type {
+ RouteRecordRaw,
+ RouteMeta
+} from 'vue-router'
+
+import type { defineComponent } from 'vue'
+
+
+export type Component = ReturnType | (() => Promise) | (() => Promise)
+
+// @ts-expect-error
+export interface AppRouter extends Omit {
+ keepAlive?: boolean
+ visible?: boolean
+ icon?: string
+ name?: string
+ key?: string
+ sort?: number
+ parent?: AppRouter | null
+ parentId?: number | string
+ menuId?: number | string
+ meta: RouteMeta
+ component?: Component | string
+ components?: Component
+ componentName?: string
+ children?: AppRouter[]
+ props?: Recordable,
+ path?: string,
+ fullPath?: string,
+ hidden?: boolean,
+ type?: string | null,
+ typeName?: string | null,
+ menuName?: string | null
+ menuType?: string | null
+}
+
+
+// export interface AppRouter {
+// keepAlive?: boolean, // 缓存
+// component?: RawRouteComponent | string | null | undefined | any, // 组件
+// children?: AppRouter[], // 子路由
+// path?: string, // 路径
+// redirect?: string, // 重定向
+// isFrame?: number, // 外链
+// target?: string // 外链打开方式 新标签打开还是当前页面打开
+// key?: string,
+// hidden: boolean, // 是否隐藏
+// title?: string // 标题
+// // visible?: boolean //
+// // icon?: string // 图标
+// name: string // 路由name
+// sort?: number // 排序
+// parentId?: number // 父节点
+// // component?: Component | string
+// // components?: Component
+// // componentName?: string //
+// hideChildrenInMenu?: boolean, // 隐藏下级节点
+// meta?: RouteMeta, // 信息
+// // props?: Recordable
+// // fullPath?: string // 全路径
+// }
diff --git a/src/router/func/index.ts b/src/router/func/index.ts
new file mode 100644
index 0000000..c70b1f2
--- /dev/null
+++ b/src/router/func/index.ts
@@ -0,0 +1,70 @@
+import { layouts, staticRouters } from "@/router/modules/routerComponents"
+import type { AppRouter } from '@/router/config/index'
+import { validURL } from "@/utils/valid"
+import type { Component } from "vue"
+import { uuid } from '@/utils/util'
+
+
+const constantRouterComponents: Component = {
+ ...layouts,
+ ...staticRouters
+}
+
+const menuTypeFormat = (type?: string) => {
+ if (!type) return null
+ const menuTypeObj: ExtraObj = {
+ 'G': 'group', // 分组
+ 'F': 'button', // 按钮
+ 'C': 'menu', // 菜单
+ 'M': 'catalogue', // 目录
+ }
+ return menuTypeObj[type] || null
+}
+
+// 构造路由
+export const generator = async (routerMap: AppRouter[] | ExtraObj[], parent:AppRouter | null): Promise => {
+ const newRouter = await Promise.all(routerMap.map(async (item: AppRouter | ExtraObj) => {
+ const { title, hideChildren, hiddenHeaderContent, hidden, icon, isFrame, target } = item.meta || {}
+ const currentRouter: AppRouter = {
+ path: item.path,
+ key: item.key,
+ fullPath: (parent ? `${parent?.fullPath ?? ''}${item.path?.includes('/') ? item.path : '/' + item.path}` : `${item.path}`),
+ // 路由名称,建议唯一
+ name: item.menuId || item.name || item.key || uuid(),
+ // 该路由对应页面的 组件(动态加载)
+ component: constantRouterComponents[item.component] || (() => import(`@/views/${item.component}`)),
+ // hideChildrenInMenu: item.hideChildrenInMenu,
+ // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
+ hidden: item.hidden,
+ parent: parent,
+ parentId: item.parentId ?? parent?.name,
+ icon: item?.meta?.icon || item.icon as string | undefined,
+ meta: {
+ icon: item?.meta?.icon || item.icon,
+ isFrame: isFrame,
+ title: title || item.menuName,
+ hideChildrenInMenu: hideChildren,
+ hiddenHeaderContent: hiddenHeaderContent,
+ // 目前只能通过判断path的http链接来判断是否外链
+ target: (validURL(item.path) ? '_blank' : ''),
+ permission: item.name,
+ hidden: hidden
+ },
+ type: item.type || menuTypeFormat(item.menuType),
+ typeName: item.typeName || null,
+ redirect: item.redirect
+ }
+ if (item.component && !constantRouterComponents[item.component]) {
+ currentRouter.path = `${parent?.path ?? ''}/${item.path}`
+ }
+ // 是否设置了隐藏子菜单
+ // 是否有子菜单,并递归处理,并将父path传入
+ if (item.children && item.children.length > 0) {
+ // Recursion
+ currentRouter.children = await generator(item.children, currentRouter)
+ }
+ return currentRouter
+ }))
+
+ return newRouter
+}
diff --git a/src/router/guard/before.ts b/src/router/guard/before.ts
new file mode 100644
index 0000000..3dd24e7
--- /dev/null
+++ b/src/router/guard/before.ts
@@ -0,0 +1,20 @@
+import { useSocketStoreWithOut } from '@/stores/modules/socket'
+
+const socketStore = useSocketStoreWithOut()
+
+
+
+// 初始化socket
+export const initSocket = () => {
+ if (!socketStore.socket) {
+ socketStore.initSocket()
+ }
+}
+
+
+// 初始化
+export const init = async () => {
+ return Promise.all([
+ await initSocket(),
+ ])
+}
diff --git a/src/router/guard/index.ts b/src/router/guard/index.ts
new file mode 100644
index 0000000..465f13c
--- /dev/null
+++ b/src/router/guard/index.ts
@@ -0,0 +1,90 @@
+import type { Router, RouteRecordRaw, RouteRecordName } from 'vue-router'
+import { useUserStore } from '@/stores/modules/user'
+import { useRouterStoreWithOut } from '@/stores/modules/async-router'
+import type { AppRouter } from '@/router/config/index'
+import { APP_VERSION } from '@/stores/modules/mutation-types'
+import { info } from '@/config/index.ts'
+import { init as beforeInit } from '@/router/guard/before.ts'
+
+const allowName = ['Login', 'LoginView', '404']
+const defaultRouter = '/'
+
+
+export function _window(key: string) {
+ if (window.hasOwnProperty('admin_config')) {
+ //@ts-ignore
+ return window.admin_config[key]
+ }
+
+ return null
+}
+
+// 版本校验
+const checkVersion = () => {
+
+}
+
+
+
+
+
+
+export const setRouteGuard = (router:Router) => {
+ router.beforeEach(async (to, from, next) => {
+ window.document.title = `${to.meta.title as string} | ${info.title}`
+ console.log('from', from)
+ // beforeInit()
+ // 校验版本
+ // checkVersion()
+ const userStore = useUserStore()
+ const token = userStore.getToken()
+ console.log('getToken', token)
+ // token失效走退出
+ // if (token && !userStore.validToken()) {
+ // // userStore.logout()
+ // }
+
+ // 无token
+ // if(!token || token === '') {
+ // console.log(to.name && allowName.includes(to.name as string))
+ // if (to.name && allowName.includes(to.name as string)) {
+ // next()
+ // } else {
+ // // next({ path: '/login/index', query: { redirect: to.fullPath } })
+ // next()
+ // }
+ // } else {
+ console.log('toROuter', to)
+ const routerStore = useRouterStoreWithOut()
+ const nowRouter = routerStore.ROUTERS
+ // 无角色, 重新初始化路由
+ // if (!userStore.permissions?.length || (userStore.permissions?.length && !nowRouter.length))
+ // if (!(userStore.permissions?.length && nowRouter.length)){
+ // 结构化路由
+ if (!(nowRouter.length)){
+ await routerStore.loadRouter().then(res => {
+ const addRoters:AppRouter[] = res
+ console.log('addRoters', addRoters)
+ // routerStore.setRouter(addRoters)
+ addRoters.map((route)=> {
+ router.addRoute(route as RouteRecordRaw)
+ })
+ })
+ next({ path: to.fullPath, replace: true, query: to.query })
+ } else {
+ // const userInfo = userStore.USERINFO ? JSON.parse(userStore.USERINFO) : {}
+ // console.log('userInfo', userInfo)
+ // if ((token || token !== '') && !userInfo.sysUserId) {
+ // userStore.getUserInfo()
+ // }
+ next()
+ // if (to.path == '/') {
+ // next({ path: defaultRouter, replace: true, query: to.query })
+
+ // } else {
+ // next()
+ // }
+ }
+ // }
+ })
+}
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 0000000..4d8be1a
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,17 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+import { globalRouter } from './base/index'
+import type { App } from 'vue'
+
+
+export const router = createRouter({
+ history: createWebHashHistory(),
+ routes: [
+ ...globalRouter
+ ],
+})
+
+
+export function setupRouter(app: App) {
+ app.use(router)
+}
+
diff --git a/src/router/modules/routerComponents.ts b/src/router/modules/routerComponents.ts
new file mode 100644
index 0000000..f624d94
--- /dev/null
+++ b/src/router/modules/routerComponents.ts
@@ -0,0 +1,22 @@
+import { defineAsyncComponent, markRaw } from 'vue'
+
+export const layouts = {
+ BaseLayout: () => import('@/components/Base/Layout/SystemLayout/index.vue'),
+ ViewLayout: () => import('@/components/Base/Layout/ViewLayout/index.vue'),
+ LoginLayout: () => import('@/components/Base/Layout/LoginLayout/index.vue'),
+}
+
+
+
+export const staticRouters = {
+ // Index: () => import('@/views/index/index.vue'),
+ // Test: () => import('@/views/test/index.vue'),
+ NotFound: defineAsyncComponent(() => import('@/views/exception/404.vue')),
+ KnowledgeChart: defineAsyncComponent(() => import('@/views/knowledge/index.vue')),
+ // DataManage: defineAsyncComponent(() => import('@/views/dataManage/index/index.vue')),
+ // ModelReasoning: defineAsyncComponent(() => import('@/views/modelReasoning/index/index.vue')),
+ // ModelTraining: defineAsyncComponent(() => import('@/views/modelTraining/index/index.vue')),
+ // ModelTrainingDetail: defineAsyncComponent(() => import('@/views/modelTraining/detail/index.vue')),
+ // ModelManage: defineAsyncComponent(() => import('@/views/setting/model/index/index.vue')),
+}
+
diff --git a/src/router/modules/staticRouter.ts b/src/router/modules/staticRouter.ts
new file mode 100644
index 0000000..e24fe75
--- /dev/null
+++ b/src/router/modules/staticRouter.ts
@@ -0,0 +1,54 @@
+export const staticRouter = [
+
+ {
+ path: '/',
+ meta: {
+ hidden: true,
+ },
+ redirect: '/knowledge-chart'
+ },
+]
+
+export const exceptionRouter = [
+ {
+ path: '/404',
+ name: '404',
+ component: 'NotFound',
+ meta: {
+ icon: '',
+ title: '',
+ hidden: true,
+ },
+ },
+ {
+ path: '/test',
+ name: 'test',
+ component: 'Test',
+ meta: {
+ icon: '',
+ title: '',
+ hidden: true,
+ },
+ },
+ {
+ name: 'KnowledgeChart',
+ path: '/knowledge-chart',
+ meta: {
+ icon: 'data',
+ title: '知识图谱',
+ hidden: false,
+ },
+ component: 'KnowledgeChart',
+ },
+ {
+ path: '/:pathMatch(.*)',
+ redirect: '/404',
+ hidden: true,
+ meta: {
+ icon: '',
+ title: '',
+ hidden: true,
+ },
+ }
+]
+
diff --git a/src/stores/counter.ts b/src/stores/counter.ts
new file mode 100644
index 0000000..b6757ba
--- /dev/null
+++ b/src/stores/counter.ts
@@ -0,0 +1,12 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+ const count = ref(0)
+ const doubleCount = computed(() => count.value * 2)
+ function increment() {
+ count.value++
+ }
+
+ return { count, doubleCount, increment }
+})
diff --git a/src/stores/index.ts b/src/stores/index.ts
new file mode 100644
index 0000000..cd30903
--- /dev/null
+++ b/src/stores/index.ts
@@ -0,0 +1,10 @@
+import type { App } from 'vue'
+import { createPinia } from 'pinia'
+
+const store = createPinia()
+
+export function setupStore(app: App) {
+ app.use(store)
+}
+
+export { store }
\ No newline at end of file
diff --git a/src/stores/modules/async-router.ts b/src/stores/modules/async-router.ts
new file mode 100644
index 0000000..ca10bfe
--- /dev/null
+++ b/src/stores/modules/async-router.ts
@@ -0,0 +1,54 @@
+import { defineStore } from "pinia"
+import { store } from '@/stores'
+import { staticRouter, exceptionRouter } from '@/router/modules/staticRouter'
+import type { AppRouter } from '@/router/config/index'
+import { generator } from '@/router/func/index'
+import { baseRouter } from '@/router/base/index'
+import { handleTree } from "@/utils/util"
+import { Row } from "ant-design-vue"
+
+interface router {
+ routerArr: AppRouter[]
+ asyncRouter: ExtraObj[]
+}
+
+
+export const useRouterStore = defineStore('router', {
+ state: (): router => ({
+ routerArr: [],
+ asyncRouter: []
+ }),
+ getters: {
+ ROUTERS(state):AppRouter[] {
+ return state.routerArr
+ },
+ ASYNCROUTERS(state): ExtraObj[] {
+ return state.asyncRouter
+ },
+ },
+ actions: {
+ setRouter(data: AppRouter[] ) {
+ this.routerArr = data
+ },
+ async loadRouter(): Promise {
+ let routers:AppRouter[] = []
+ return new Promise(async (resolve) => {
+
+ // 组装
+ const menuNav: ExtraObj[] = []
+ baseRouter.children = staticRouter.concat() as AppRouter[]
+ menuNav.push(baseRouter)
+ menuNav.push(...exceptionRouter)
+ // menuNav.push(...globalRouter)
+ routers = await generator(menuNav, null)
+ console.log('routers', routers)
+ this.routerArr = routers
+ resolve(routers)
+ })
+ }
+ },
+})
+
+export const useRouterStoreWithOut = () => {
+ return useRouterStore(store)
+}
diff --git a/src/stores/modules/mutation-types.ts b/src/stores/modules/mutation-types.ts
new file mode 100644
index 0000000..01daca2
--- /dev/null
+++ b/src/stores/modules/mutation-types.ts
@@ -0,0 +1,6 @@
+
+export const ACCESS_TOKEN = 'token'
+export const ACCESS_EXPIRES = 'expires'
+export const APP_VERSION = 'app_version'
+
+
diff --git a/src/stores/modules/socket.ts b/src/stores/modules/socket.ts
new file mode 100644
index 0000000..54f9e07
--- /dev/null
+++ b/src/stores/modules/socket.ts
@@ -0,0 +1,78 @@
+import { defineStore, acceptHMRUpdate } from "pinia"
+import { store } from '@/stores'
+import { socketInit } from '@/utils/socket/index.ts'
+import type { Socket } from "socket.io-client"
+
+interface UserState {
+ socket: Socket | null,
+ connected:any,
+ reconnectAttempts: number,
+}
+
+export const useSocketStore = defineStore('socket', {
+ state: (): UserState => ({
+ socket: null,
+ connected: false,
+ reconnectAttempts: 0,
+ }),
+ getters: {
+ SOCKET(state) {
+ return state.socket
+ },
+ isConnected(state) {
+ return state.connected
+ },
+ attempts(state) {
+ return state.reconnectAttempts
+ }
+ },
+ actions: {
+ // 获取token
+ initSocket() {
+ console.log('?initSocket')
+ const socket = socketInit(true)
+ this.$patch((state) => {
+ state.socket = socket
+ })
+ // ===== 事件监听 =====
+
+ socket.on('connect', () => {
+ this.connected = true
+ console.log('[Socket] 已连接,socket id:', socket.id)
+ })
+
+ socket.on('disconnect', (reason) => {
+ this.connected = false
+ console.log(`[Socket] 已断开,原因: ${reason}`)
+ })
+
+ // 重连中,每次尝试都会触发
+ socket.io.on('reconnect_attempt', (attempt: number) => {
+ this.reconnectAttempts = attempt
+ console.log(`[Socket] 重连尝试 #${attempt}`)
+ })
+
+ // 重连成功
+ socket.io.on('reconnect', (attempt: number) => {
+ console.log(`[Socket] 重连成功,尝试次数: ${attempt}`)
+ })
+
+ // 重连失败
+ socket.io.on('reconnect_failed', () => {
+ console.warn('[Socket] 重连失败,放弃继续重连')
+ })
+ },
+ // 清除仓库数据
+ cleanStore() {
+ this.$reset()
+ }
+ },
+})
+
+if (import.meta.hot) {
+ import.meta.hot.accept(acceptHMRUpdate(useSocketStore, import.meta.hot))
+}
+
+export const useSocketStoreWithOut = () => {
+ return useSocketStore(store)
+}
diff --git a/src/stores/modules/system.ts b/src/stores/modules/system.ts
new file mode 100644
index 0000000..afd8fea
--- /dev/null
+++ b/src/stores/modules/system.ts
@@ -0,0 +1,22 @@
+import { defineStore } from "pinia"
+import { store } from '@/stores'
+
+
+interface info {
+}
+
+
+export const systemStore = defineStore('system', {
+ state: (): info => ({
+ }),
+ getters: {
+
+ },
+ actions: {
+
+ },
+})
+
+export const systemStoreWithOut = () => {
+ return systemStore(store)
+}
diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts
new file mode 100644
index 0000000..7922a1e
--- /dev/null
+++ b/src/stores/modules/user.ts
@@ -0,0 +1,103 @@
+import { defineStore, acceptHMRUpdate } from "pinia"
+import { store } from '@/stores'
+import { ACCESS_TOKEN, ACCESS_EXPIRES, USER_INFO } from '@/stores/modules/mutation-types'
+// import { loginApi } from '@/apis/auths/index'
+import { notification } from 'ant-design-vue';
+import { useRouterStoreWithOut } from './async-router'
+
+interface LoginForm {
+ email: string;
+ password: string;
+}
+
+interface UserState {
+ token: string | null
+ role: string | null
+ permissions: {
+ [key: string]: ExtraObj
+ },
+ userInfo?: ExtraObj,
+}
+
+export const useUserStore = defineStore('user', {
+ state: (): UserState => ({
+ token: '',
+ role: null,
+ permissions: {},
+ userInfo: {},
+ }),
+ getters: {
+ ROLE(state) {
+ return state.role
+ },
+ TOKEN(): string {
+ return this.getToken()
+ },
+ USERINFO(state) {
+ return state.userInfo
+ },
+
+ },
+ actions: {
+ // 获取token
+ getToken() {
+ const raw = localStorage.getItem(ACCESS_TOKEN) || ''
+ return raw
+ },
+ // 设置token
+ setToken(token: string) {
+ console.log('ACCESS_TOKEN', ACCESS_TOKEN)
+ localStorage.setItem(ACCESS_TOKEN, token || '')
+ },
+ // 设置用户数据
+ setUserInfo(data: ExtraObj | null) {
+ this.$patch((state) => {
+ if (data) {
+ state.userInfo = data
+ state.permissions = data.permissions
+ state.role = data.role
+ } else {
+ state.userInfo = {}
+ state.permissions = {}
+ state.role = null
+ state.userInfo = {}
+ }
+
+ })
+ },
+ // 登录
+ async login(data: LoginForm) {
+ // let res: ExtraObj
+ // res = await loginApi(data)
+ // .catch((err) => {
+ // notification.error({
+ // message: '登录失败',
+ // description: err.message,
+ // })
+ // return err
+ // })
+ // if (res.code == 200) {
+ // this.setToken(res.data.token)
+ // this.setUserInfo(res.data)
+ // this.getUserInfo()
+ // }
+ // return res
+ },
+ // 获取用户信息
+ async getUserInfo() {
+
+ },
+ // 退出
+ logout() {
+
+ },
+ },
+})
+
+if (import.meta.hot) {
+ import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
+}
+
+export const useUserStoreWithOut = () => {
+ return useUserStore(store)
+}
diff --git a/src/styles/index.css b/src/styles/index.css
new file mode 100644
index 0000000..bbc22d8
--- /dev/null
+++ b/src/styles/index.css
@@ -0,0 +1,30 @@
+@charset "UTF-8";
+#app {
+ padding: 0;
+ margin: 0;
+ max-width: 100%;
+ width: 100%;
+ height: 100vh;
+}
+
+p {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+button {
+ margin: 0;
+ /* 清除外边距 */
+ padding: 0;
+ /* 清除内边距 */
+ border: none;
+ /* 去除边框 */
+ background: none;
+ /* 清除背景色 */
+ font: inherit;
+ /* 继承父元素字体 */
+ color: inherit;
+ /* 继承父元素文字颜色 */
+ cursor: pointer;
+ /* 保持指针样式 */
+}
diff --git a/src/styles/index.scss b/src/styles/index.scss
new file mode 100644
index 0000000..a44ae04
--- /dev/null
+++ b/src/styles/index.scss
@@ -0,0 +1,34 @@
+html,
+body,
+p {
+}
+
+#app {
+ padding: 0;
+ margin: 0;
+ max-width: 100%;
+ width: 100%;
+ height: 100vh;
+}
+
+p {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+a,
+a:hover,
+a:visited,
+a:link,
+a:active {
+}
+
+button {
+ margin: 0; /* 清除外边距 */
+ padding: 0; /* 清除内边距 */
+ border: none; /* 去除边框 */
+ background: none; /* 清除背景色 */
+ font: inherit; /* 继承父元素字体 */
+ color: inherit; /* 继承父元素文字颜色 */
+ cursor: pointer; /* 保持指针样式 */
+}
diff --git a/src/styles/theme/default.json b/src/styles/theme/default.json
new file mode 100644
index 0000000..abf96df
--- /dev/null
+++ b/src/styles/theme/default.json
@@ -0,0 +1,30 @@
+{
+ "token": {
+ "fontSize": 16,
+ "wireframe": false,
+ "fontSizeXL": 20,
+ "fontSizeHeading1": 36,
+ "fontSizeHeading2": 20,
+ "fontSizeHeading3": 16,
+ "fontSizeHeading4": 14,
+ "fontSizeHeading5": 12,
+ "lineHeight": 1.5,
+ "sizeStep": 4,
+ "borderRadiusLG": 16
+ },
+ "components": {
+ "Dropdown": {
+ "borderRadiusLG": 6,
+ "fontSize": 14,
+ "fontSizeSM": 12,
+ "lineWidthBold": 2
+ },
+ "Menu": {
+ "zIndexPopup": 1050,
+ "dropdownWidth": 260,
+ "radiusItem": 6
+ },
+ "Pagination": {},
+ "Cascader": {}
+ }
+}
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
new file mode 100644
index 0000000..688a850
--- /dev/null
+++ b/src/types/global.d.ts
@@ -0,0 +1,37 @@
+import type { DefineComponent } from 'vue'
+
+
+declare global {
+ type ExtraObj = {
+ [key?: string]: any
+ }
+ type Recordable = Record
+
+ declare module '*.csv' {
+ const value: any;
+ export default value;
+ }
+}
+
+
+declare module '*.jpg' {
+ const value: any;
+ export default value;
+}
+
+declare module '*.png' {
+ const value: any;
+ export default value;
+}
+
+
+
+declare module '*.vue' {
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
+
+declare module '@components/*';
+
+
+
diff --git a/src/types/module.d.ts b/src/types/module.d.ts
new file mode 100644
index 0000000..5468342
--- /dev/null
+++ b/src/types/module.d.ts
@@ -0,0 +1,37 @@
+declare module '*.vue' {
+ import { DefineComponent } from 'vue'
+ const Component: DefineComponent<{}, {}, any>
+ export default Component
+}
+
+// declare module 'ant-design-vue/es/locale/*' {
+// import { Locale } from 'ant-design-vue/types/locale-provider'
+// const locale: Locale & ReadonlyRecordable
+// export default locale as Locale & ReadonlyRecordable
+// }
+
+declare module 'virtual:*' {
+ const result: any
+ export default result
+}
+
+declare module 'lodash' {
+ const content: any
+ export = content
+}
+declare module '*.ts' {
+ const content: any
+ export = content
+}
+
+declare module 'hevue-img-preview' {
+ const content: any
+ export = content
+}
+
+declare module 'fabric-with-erasing' {
+ const content: any
+ export = content
+}
+
+declare module '@ant-design/icons-vue';
diff --git a/src/utils/EvenStream/index.ts b/src/utils/EvenStream/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/utils/download/index.ts b/src/utils/download/index.ts
new file mode 100644
index 0000000..97c2d48
--- /dev/null
+++ b/src/utils/download/index.ts
@@ -0,0 +1,187 @@
+// import { getEnv } from '@/lib/utils/env/index.ts'
+import { request } from '@/utils/request/index.ts';
+
+
+
+// 创建一个a标签,并做点击下载事件
+export function downloadFile(hrefUrl:any, fileName:any){
+ console.log('downloadFile',hrefUrl,fileName);
+ const a = document.createElement('a')
+ a.href = hrefUrl
+ a.target = '_blank' // 新窗口打开 20250320 yjc 新增
+ a.download = fileName // 下载后文件名
+ document.body.appendChild(a)
+ a.click() // 点击下载
+ document.body.removeChild(a) // 下载完成移除元素
+}
+// 封装blob对象
+function dataURLToBlob(base64Str:any, mimeTypeStr:any) {
+ const bstr = window.atob(base64Str); // 解码 base-64 编码的字符串,base-64 编码使用方法是 btoa()
+ let length = bstr.length;
+ const u8arr = new Uint8Array(length); // 创建初始化为0的,包含length个元素的无符号整型数组
+ while (length--) {
+ u8arr[length] = bstr.charCodeAt(length); // 返回在指定的位置的字符的 Unicode 编码
+ }
+ return new Blob([u8arr], { type: mimeTypeStr }); // 返回一个blob对象
+}
+
+// 后端返回base64公共导出
+export function downloadFileByBase64(base64Str:any, mimeTypeStr:any, fileName:any){
+ const myBlob = dataURLToBlob(base64Str, mimeTypeStr)
+ const myUrl = window.URL.createObjectURL(myBlob)
+ downloadFile(myUrl, fileName)
+}
+// 后端返回文件流公共导出
+export function downloadFileByFileFlow(blobData:any, mimeTypeStr:any, fileName:any) {
+ const blob = new Blob([blobData], { type: mimeTypeStr })
+ const hrefUrl = window.URL.createObjectURL(blob) // 创建下载的链接
+ downloadFile(hrefUrl, fileName);
+}
+
+//将base64转换为文件
+export function base64Tofile(dataurl:any,fileName:any){
+ console.log(dataurl,fileName);
+ // debugger;
+ let arr = dataurl.split(','),
+ mime = arr[0].match(/:(.*?);/)[1],
+ bstr = atob(arr[1]),
+ n = bstr.length,
+ u8arr = new Uint8Array(n);
+ while (n--) {
+ u8arr[n] = bstr.charCodeAt(n);
+ }
+ const blob =new Blob([u8arr], { type: mime });
+ // @ts-ignore
+ blob.lastModifiedDate = new Date();
+ // @ts-ignore
+ blob.name = fileName;
+
+ console.log(blob,'blob');
+ download(blob)
+ // window.location.replace(blobUrl)
+
+ return blob;
+}
+
+
+export function download(downfile:any, fileName?: string) {
+ const tmpLink = document.createElement("a");
+ const objectUrl = URL.createObjectURL(downfile);
+
+ tmpLink.href = objectUrl;
+ tmpLink.target = '_blank' // 新窗口打开 20250320 yjc 新增
+ tmpLink.download = fileName || downfile.name;
+ document.body.appendChild(tmpLink);
+ tmpLink.click();
+
+ document.body.removeChild(tmpLink);
+ URL.revokeObjectURL(objectUrl);
+}
+
+// 生成唯一的uuid
+export function getUUID(randomLength:any) {
+ function S4() {
+ return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
+ }
+ return ("p"+S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4())
+}
+
+export function downloadWav(base64Data:any,name:any) {
+ // const tmpLink = document.createElement("a");
+ // const objectUrl = URL.createObjectURL(downfile);
+
+ // tmpLink.href = objectUrl;
+ // tmpLink.download = downfile.name;
+ // document.body.appendChild(tmpLink);
+ // tmpLink.click();
+
+ // document.body.removeChild(tmpLink);
+ // URL.revokeObjectURL(objectUrl);
+ const downloadLink = document.createElement('a');
+ downloadLink.href = base64Data; // 这里的 base64Data 是之前转换得到的 Base64 字符串
+ downloadLink.download = name; // 设置下载文件的名称
+ downloadLink.click(); // 模拟点击下载链接
+ }
+
+export function saveDatatoJson(data:any, filename:any) {
+ if (!data) {
+ console.error('Console.save: No data')
+ return;
+ }
+ if (!filename) filename = 'console.json'
+ if (typeof data === "object") {
+ data = JSON.stringify(data, undefined, 4)
+ }
+ const blob = new Blob([data], {
+ type: 'text/json'
+ }),
+ e = document.createEvent('MouseEvents'),
+ a = document.createElement('a')
+ a.download = filename
+ a.href = window.URL.createObjectURL(blob)
+ a.dataset.downloadurl = ['text/json', a.download, a.href].join(':')
+ e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
+ a.dispatchEvent(e)
+}
+// file 文件转 base64
+export function fileToBase64(file:any) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(file);
+ reader.onload = () => {
+ resolve(reader.result);
+ };
+ reader.onerror = (error) => {
+ reject(error);
+ };
+ });
+}
+// file 文件转换成 字节流
+export function fileToBytes(file:any) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.readAsArrayBuffer(file);
+ reader.onload = () => {
+ resolve(reader.result);
+ };
+ reader.onerror = (error) => {
+ reject(error);
+ };
+ });
+}
+
+// base64转文件
+export function base64ToFile(base64Data: string, fileName: string, mimeType?: string): File {
+ // 从base64字符串中提取数据部分和MIME类型
+ let dataType = '';
+ let base64Content = '';
+
+ if (base64Data.includes(';base64,')) {
+ const parts = base64Data.split(';base64,');
+ dataType = parts[0].split(':')[1];
+ base64Content = parts[1];
+ } else {
+ // 如果base64字符串不包含MIME信息,则使用传入的mimeType
+ dataType = mimeType || 'application/octet-stream';
+ base64Content = base64Data;
+ }
+
+ // 解码base64
+ const byteCharacters = atob(base64Content);
+ const byteArrays = [];
+
+ // 将字符串转换为字节数组
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteArrays.push(byteCharacters.charCodeAt(i));
+ }
+
+ // 创建Uint8Array
+ const uint8Array = new Uint8Array(byteArrays);
+
+ // 创建Blob对象
+ const blob = new Blob([uint8Array], { type: dataType });
+
+ // 创建File对象
+ return new File([blob], fileName, { type: dataType, lastModified: new Date().getTime() });
+}
+
diff --git a/src/utils/request/index.ts b/src/utils/request/index.ts
new file mode 100644
index 0000000..5cb5bc5
--- /dev/null
+++ b/src/utils/request/index.ts
@@ -0,0 +1,106 @@
+import { Config, RequestHead, ResponseData, ResponseError, RequestParams } from '@/utils/request/types/index.ts'
+import { message } from "ant-design-vue";
+
+// 默认配置
+const createDefaultConfig = (): {
+ headers: RequestHead
+} => {
+ const defaultConfig = {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ authorization: `Bearer ${localStorage.token}`
+ },
+ }
+ return defaultConfig
+}
+
+// 构造数据
+const genData = (data: any, dataRaw: boolean) => {
+ if (dataRaw) {
+ return data instanceof FormData || data instanceof URLSearchParams
+ ? data
+ : new URLSearchParams(data);
+ }
+ return JSON.stringify(data);
+};
+
+// 构造返回
+const genResult = (result: ExtraObj, status: 'success' | 'error'): ResponseData | ResponseError => {
+ if (result.code) {
+ return {
+ ...result,
+ code: result.code,
+ data: result.data
+ }
+ } else {
+ if (status == 'success') {
+ return {
+ code: 200,
+ data: result
+ }
+ } else {
+ let error
+ if ('detail' in result) {
+ error = result.detail
+ } else {
+ error = result
+ }
+ return {
+ code: 500,
+ message: error,
+ _raw: result
+ }
+ }
+
+ }
+}
+
+// 构造请求参数
+const createRuestParams = (config: Config) => {
+ const { method, data, dataRaw, headers, controller } = config;
+ const requetParams: RequestParams = {
+ method,
+ headers: headers as HeadersInit ?? createDefaultConfig().headers,
+ body: data instanceof URLSearchParams ? data : genData(data, dataRaw || false),
+ signal: controller?.signal
+ };
+ return requetParams
+}
+
+// 请求
+export const request = async (config: Config): Promise => {
+ const { url, base, params, getRaw } = config;
+ // 路径参数格式化
+ const urlParams = params ? new URLSearchParams(params) : null
+ const extra = `${urlParams ? '?' + urlParams.toString() : ''}`
+ const requestUrl = `${base || import.meta.env.VITE_BASE_API}${url}${extra}`
+ const requestParams = createRuestParams(config)
+
+ // 请求逻辑封装
+ const res = await fetch(requestUrl, requestParams).then(async (res) => {
+ // 是否需要返回原始数据
+ const result = getRaw ? res : await res.json();
+ if (!res.ok) throw result
+ if (result?.code && result?.code !== 200) {
+ throw result
+ }
+ return getRaw ? result : genResult(result, 'success')
+ }).catch((err) => {
+ if (err.name == 'AbortError') {
+ return Promise.reject(err)
+ }
+ let error
+ if ('code' in err) {
+ error = err
+ } else {
+ error = genResult(err, 'error')
+ }
+ if (error.message) {
+ message.error(error.message)
+ }
+ return Promise.reject(error)
+ });
+
+ return res;
+}
diff --git a/src/utils/request/types/index.ts b/src/utils/request/types/index.ts
new file mode 100644
index 0000000..8ad4ad7
--- /dev/null
+++ b/src/utils/request/types/index.ts
@@ -0,0 +1,43 @@
+export interface Config {
+ base?: string, // 前缀
+ url: string // 请求地址
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
+ data?: string | Record // 请求体
+ dataRaw?: boolean // 请求体是否为原始数据
+ params?: string | Record | URLSearchParams | string[][] // 请求参数
+ headers?: HeadersInit | undefined // 请求头
+ controller?: AbortController // 请求控制器
+ getRaw?: boolean // 是否需要返回原始数据
+}
+
+// 请求头
+export interface RequestHead {
+ [key: string]: string
+}
+
+// 标准响应
+export interface ResponseData {
+ code: number
+ data: T
+ [key: string]: any
+}
+
+// 错误响应
+export interface ResponseError {
+ code: number
+ message?: string
+ _raw: T
+}
+
+// 请求参数
+export interface RequestParams extends RequestInit {
+ signal?: AbortSignal
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
+ headers: HeadersInit
+ body?: BodyInit | null
+ [key: string]: any
+}
+
+
+
+
diff --git a/src/utils/socket/index.ts b/src/utils/socket/index.ts
new file mode 100644
index 0000000..90b0dc0
--- /dev/null
+++ b/src/utils/socket/index.ts
@@ -0,0 +1,19 @@
+import { io } from 'socket.io-client';
+import { Socket } from 'socket.io-client/build/esm/socket'
+// 设置socket链接
+export const socketInit = (enableWebsocket: boolean) => {
+ // const socketUrl = import.meta.env.VITE_SOCKET_URL;
+
+ const _socket: Socket = io(undefined, {
+ reconnection: true,
+ reconnectionDelay: 1000,
+ reconnectionDelayMax: 5000,
+ randomizationFactor: 0.5,
+ path: '/ws/socket.io',
+ transports: enableWebsocket ? ['websocket'] : ['polling', 'websocket'],
+ auth: { token: localStorage.token }
+ });
+ console.log('_socket', _socket)
+ return _socket
+}
+
diff --git a/src/utils/text/index.ts b/src/utils/text/index.ts
new file mode 100644
index 0000000..8f42f2f
--- /dev/null
+++ b/src/utils/text/index.ts
@@ -0,0 +1,48 @@
+
+// 复制文本
+/**
+ * 复制文本到剪贴板
+ * @param text 要复制的文本
+ * @returns 是否复制成功
+ */
+export const copyToClipboard = async (text: string) => {
+ let result = false;
+ if (!navigator.clipboard) {
+ const textArea = document.createElement('textarea');
+ textArea.value = text;
+
+ // Avoid scrolling to bottom
+ textArea.style.top = '0';
+ textArea.style.left = '0';
+ textArea.style.position = 'fixed';
+
+ document.body.appendChild(textArea);
+ textArea.focus();
+ textArea.select();
+
+ try {
+ const successful = document.execCommand('copy');
+ const msg = successful ? 'successful' : 'unsuccessful';
+ console.log('Fallback: Copying text command was ' + msg);
+ result = true;
+ } catch (err) {
+ console.error('Fallback: Oops, unable to copy', err);
+ }
+
+ document.body.removeChild(textArea);
+ return result;
+ }
+
+ result = await navigator.clipboard
+ .writeText(text)
+ .then(() => {
+ console.log('Async: Copying to clipboard was successful!');
+ return true;
+ })
+ .catch((error) => {
+ console.error('Async: Could not copy text: ', error);
+ return false;
+ });
+
+ return result;
+};
diff --git a/src/utils/transition/file.ts b/src/utils/transition/file.ts
new file mode 100644
index 0000000..51effca
--- /dev/null
+++ b/src/utils/transition/file.ts
@@ -0,0 +1,21 @@
+// 文件格式转换
+export const sizeFormat = (
+ number: number | string,
+ start: number = 0,
+ max: number = 999,
+ showUnit: boolean = true,
+ decimal: number = 2
+) => {
+ const unit = ['b', 'Kb', 'Mb', 'Gb', 'Tb']
+ let limitUnitIndex = Math.min(max, unit.length - 1)
+ let unitIndex = start
+ let useNumber = Number(number)
+ console.log('useNumber', useNumber)
+ while (useNumber >= 1024 && unitIndex < limitUnitIndex) {
+ useNumber /= 1024
+ unitIndex++
+ }
+ console.log('useNumber', useNumber)
+
+ return `${Math.ceil(useNumber).toFixed(decimal)}${showUnit ? unit[unitIndex] : ''}`
+}
diff --git a/src/utils/util.ts b/src/utils/util.ts
new file mode 100644
index 0000000..19842cc
--- /dev/null
+++ b/src/utils/util.ts
@@ -0,0 +1,64 @@
+
+export function uuid() {
+ let d = Date.now()
+ if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
+ d += performance.now() // use high-precision timer if available
+ }
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ const r = (d + Math.random() * 16) % 16 | 0
+ d = Math.floor(d / 16)
+ return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
+ })
+}
+
+/**
+ * 构造树型结构数据
+ * @param {*} data 数据源
+ * @param {string} id id字段 默认 'id'
+ * @param {string} parentId 父节点字段 默认 'parentId'
+ * @param {*} children 孩子节点字段 默认 'children'
+ * @param {string | number} rootId 根Id 默认 0
+ */
+export function handleTree(data: ExtraObj[], id?: string, parentId?: string, children?: string, rootId?: number | string) {
+ const reNameId = id || 'id'
+ const reNameParentId = parentId || 'parentId'
+ const reNameChildren = children || 'children'
+ const reNameRootId =
+ rootId ||
+ Math.min.apply(
+ Math,
+ data.map(item => {
+ return item[reNameParentId]
+ })
+ ) ||
+ 0
+ // 对源数据深度克隆
+ const cloneData = JSON.parse(JSON.stringify(data))
+ // 循环所有项
+ const treeData = cloneData.filter((father: ExtraObj) => {
+ var branchArr = cloneData.filter((child: ExtraObj) => {
+ // 返回每一项的子级数组
+ return father[reNameId] === child[reNameParentId]
+ })
+ if (branchArr.length > 0) {
+ father[reNameChildren] = branchArr
+ } else {
+ father[reNameChildren] = ''
+ }
+ // 返回第一层
+ return father[reNameParentId] == reNameRootId
+ })
+ return treeData !== '' ? treeData : data
+}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/utils/valid.ts b/src/utils/valid.ts
new file mode 100644
index 0000000..c30fc51
--- /dev/null
+++ b/src/utils/valid.ts
@@ -0,0 +1,23 @@
+// 网址校验
+export const httpReg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+)\.)+([A-Za-z0-9-~/])+$/
+
+export function validURL (url?:string) {
+ const reg = /^([hH][tT]{2}[pP]:\/\/|[hH][tT]{2}[pP][sS]:\/\/)(([A-Za-z0-9-~]+)\.)+([A-Za-z0-9-~/])+$/
+ return reg.test(url||'')
+}
+
+// 手机校验
+export const phoneReg = /^1[3456789]\d{9}$/
+
+export const MobReg = /^((0\d{2,3})-)?(\d{7,8})$/
+
+export function validPhone (phone: string) {
+ return phoneReg.test(phone) || MobReg.test(phone)
+}
+
+// 邮箱校验
+export const mailReg = /^[a-zA-Z0-9]+([._\\-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9]+([._\\-]?[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/
+
+export function validMail (mail: string) {
+ return mailReg.test(mail)
+}
diff --git a/src/views/exception/404.vue b/src/views/exception/404.vue
new file mode 100644
index 0000000..23c0bd8
--- /dev/null
+++ b/src/views/exception/404.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/src/views/index/index.vue b/src/views/index/index.vue
new file mode 100644
index 0000000..37e4804
--- /dev/null
+++ b/src/views/index/index.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/src/views/knowledge/components/Chat/chat.vue b/src/views/knowledge/components/Chat/chat.vue
new file mode 100644
index 0000000..fec54d0
--- /dev/null
+++ b/src/views/knowledge/components/Chat/chat.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/knowledge/hooks/chart.ts b/src/views/knowledge/hooks/chart.ts
new file mode 100644
index 0000000..3790039
--- /dev/null
+++ b/src/views/knowledge/hooks/chart.ts
@@ -0,0 +1,342 @@
+import { ref } from "vue";
+
+// 关系图逻辑
+export const useChartHooks = () => {
+ const option = ref({
+ title: {
+ text: "",
+ },
+ tooltip: {
+ formatter: (params: Record) => {
+ // params.data包含节点的数据信息
+ if (params.data && params.data.raw) {
+ // 显示指定字段,这里假设我们要显示raw对象中的特定字段
+ // 可以根据实际需求调整要显示的字段
+ const rawData = params.data.raw;
+ let result = '';
+
+ // 显示ID(作为唯一标识)
+ if (rawData.id) {
+ result += `ID: ${rawData.id}
`;
+ }
+
+ // 显示名称
+ if (rawData.properties?.name || rawData.name) {
+ const displayName = rawData.properties?.name || rawData.name;
+ result += `名称: ${displayName}
`;
+ }
+
+
+
+ // 可以添加更多需要显示的字段
+ // 例如:
+ // if (rawData.properties?.description) {
+ // result += `描述: ${rawData.properties.description}
`;
+ // }
+
+ return result;
+ }
+ // 回退到默认显示
+ return params.name || '未知节点';
+ },
+ },
+ animation: false, // 完全关闭动画
+ animationDuration: 0, // 动画时长设为0
+ animationDurationUpdate: 0, // 更新动画时长设为0
+ animationEasingUpdate: "linear", // 使用线性缓动
+ force: {
+ repulsion: 1000,
+ gravity: 0.1, // 添加重力防止节点飞散
+ friction: 0.6, // 添加摩擦力
+ layoutAnimation: false, // 关闭力导向布局动画
+ },
+ // animationDurationUpdate: 100,
+ // animationEasingUpdate: "quinticInOut",
+ label: {
+ normal: {
+ show: true,
+ textStyle: {
+ fontSize: 12,
+ },
+ },
+ },
+ legend: {
+ x: "center",
+ show: false,
+ // data: ["夫妻", "战友", "亲戚"],
+ },
+ series: [
+ {
+ type: "graph",
+ layout: "force",
+ symbolSize: 60,
+ focusNodeAdjacency: true,
+ roam: true,
+ categories: [
+ {
+ name: "夫妻",
+ // itemStyle: {
+ // normal: {
+ // color: "#009800",
+ // },
+ // },
+ },
+ {
+ name: "战友",
+ // itemStyle: {
+ // normal: {
+ // color: "#4592FF",
+ // },
+ // },
+ },
+ {
+ name: "亲戚",
+ // itemStyle: {
+ // normal: {
+ // color: "#3592F",
+ // },
+ // },
+ },
+ ],
+ label: {
+ normal: {
+ show: true,
+ textStyle: {
+ fontSize: 12,
+ },
+ formatter: (e: Record) => {
+ const name = e.data.raw.properties.name
+ return name;
+ }
+ },
+ },
+ force: {
+ repulsion: 1000,
+ },
+ edgeSymbolSize: [4, 50],
+ edgeLabel: {
+ normal: {
+ show: true,
+ textStyle: {
+ fontSize: 10,
+ },
+ formatter: "{c}",
+ },
+ },
+ data: [
+ // {
+ // name: "徐贱云",
+ // draggable: true,
+ // },
+ // {
+ // name: "冯可梁",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "邓志荣",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "李荣庆",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "郑志勇",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "赵英杰",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "王承军",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "陈卫东",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "邹劲松",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "赵成",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "陈现忠",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "陶泳",
+ // category: 1,
+ // draggable: true,
+ // },
+ // {
+ // name: "王德福",
+ // category: 1,
+ // draggable: true,
+ // },
+ ],
+ links: [
+ // {
+ // source: 0,
+ // target: 1,
+ // value: "asd",
+ // },
+ // {
+ // source: 0,
+ // target: 2,
+ // value: "子女",
+ // },
+ // {
+ // source: 0,
+ // target: 3,
+ // value: "夫妻",
+ // },
+ // {
+ // source: 0,
+ // target: 4,
+ // value: "父母",
+ // },
+ // {
+ // source: 1,
+ // target: 2,
+ // value: "表亲",
+ // },
+ // {
+ // source: 0,
+ // target: 5,
+ // value: "朋友",
+ // },
+ // {
+ // source: 4,
+ // target: 5,
+ // value: "朋友",
+ // },
+ // {
+ // source: 2,
+ // target: 8,
+ // value: "叔叔",
+ // },
+ // {
+ // source: 0,
+ // target: 12,
+ // value: "朋友",
+ // },
+ // {
+ // source: 6,
+ // target: 11,
+ // value: "爱人",
+ // },
+ // {
+ // source: 6,
+ // target: 3,
+ // value: "朋友",
+ // },
+ // {
+ // source: 7,
+ // target: 5,
+ // value: "朋友",
+ // },
+ // {
+ // source: 9,
+ // target: 10,
+ // value: "朋友",
+ // },
+ // {
+ // source: 3,
+ // target: 10,
+ // value: "朋友",
+ // },
+ // {
+ // source: 2,
+ // target: 11,
+ // value: "同学",
+ // },
+ ],
+ lineStyle: {
+ normal: {
+ opacity: 0.9,
+ width: 1,
+ curveness: 0,
+ },
+ },
+ },
+ ],
+ });
+
+ /**
+ * @desc 描述
+ * @auth yjc
+ * @created 2025-11-17 15:25
+ * @since v1.0.0
+ * @param {string} id - 当前数据的唯一标识
+ * @param {string} 参数描述
+ * @returns {类型} 返回值描述
+ *
+ * @example
+ * functionName(参数)
+ *
+ * @modified 2025-11-17 15:25
+ * @desc 描述
+ */
+
+ const createLink = (data: Record) => {
+ return {
+ raw: data,
+ source: data.parentIndex,
+ target: data.index,
+ value: data.type,
+ itemStyle: data?.itemStyle || null
+ }
+ }
+
+ // 构造节点数据
+ const createPointData = (data: Record, options?: Record) => {
+ return {
+ raw: data,
+ id: data.id,
+ name: data.name,
+ draggable: options?.draggable || false,
+ itemStyle: data?.itemStyle || null
+ }
+ }
+
+ // 类型
+ // const createType = (data: Record) => {
+ // const colorMap: Record = {
+ // 夫妻: "#009800",
+ // 战友: "#4592FF",
+ // 亲戚: "#3592F",
+ // }
+
+ // return {
+ // name: data.type,
+ // itemStyle: {
+ // normal: {
+ // color: colorMap[data.type] || "#009800",
+ // }
+ // },
+ // }
+ // }
+
+
+
+ return {
+ option,
+ createLink,
+ createPointData,
+ };
+};
diff --git a/src/views/knowledge/hooks/utils.ts b/src/views/knowledge/hooks/utils.ts
new file mode 100644
index 0000000..348d7fd
--- /dev/null
+++ b/src/views/knowledge/hooks/utils.ts
@@ -0,0 +1,40 @@
+export const useUtils = () => {
+
+ // 存储已生成的颜色,确保不重复
+ const usedColors = new Set();
+
+ // 生成随机颜色
+ const generateColor = (): string => {
+ // 生成更亮的颜色,避免太暗
+ const r = Math.floor(Math.random() * 155) + 100
+ const g = Math.floor(Math.random() * 155) + 100
+ const b = Math.floor(Math.random() * 155) + 100
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
+ }
+
+ // 生成颜色
+ const createColor = () => {
+ let color: string
+ let attempts = 0
+
+ // 尝试生成不重复的颜色,最多尝试100次
+ do {
+ color = generateColor()
+ attempts++
+ } while (usedColors.has(color) && attempts < 100)
+
+ // 如果尝试次数过多,清空已使用的颜色重新开始
+ if (attempts >= 100) {
+ usedColors.clear()
+ color = generateColor()
+ }
+
+ usedColors.add(color)
+ return color
+ }
+
+ return {
+ createColor
+ }
+
+}
diff --git a/src/views/knowledge/index.vue b/src/views/knowledge/index.vue
new file mode 100644
index 0000000..af1acb9
--- /dev/null
+++ b/src/views/knowledge/index.vue
@@ -0,0 +1,312 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
节点名称:
+
{{ currentNode.properties.name }}
+
+
+
+
总结:
+
+ {{ currentNode.properties.summary }}
+ {{ currentNode.properties.summary }}
+
+
+
+
切片:
+
+ {{ currentNode.properties.origin_text }}
+ {{ currentNode.properties.origin_text }}
+
+
+
+
创建时间:
+
+ {{ dayjs(currentNode.properties.created_at).format("YYYY-MM-DD hh:mm:ss") }}
+
+
+
更新时间:
+
+ {{ dayjs(currentNode.properties.last_updated).format("YYYY-MM-DD hh:mm:ss") }}
+
+
+
+
+
+
+
节点名称:
+
{{ item.node.properties.name }}
+
+
+
关系:
+
{{ item.type }}
+
+
+
总结:
+
+ {{ item.node.properties.summary }}
+ {{ item.node.properties.summary }}
+
+
+
+
切片:
+
+ {{ item.node.properties.origin_text }}
+ {{ item.node.properties.origin_text }}
+
+
+
+
创建时间:
+
+ {{ dayjs(item.node.properties.created_at).format("YYYY-MM-DD hh:mm:ss") }}
+
+
+
更新时间:
+
+ {{ dayjs(item.node.properties.last_updated).format("YYYY-MM-DD hh:mm:ss") }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/knowledge/services/chart.ts b/src/views/knowledge/services/chart.ts
new file mode 100644
index 0000000..5bb2698
--- /dev/null
+++ b/src/views/knowledge/services/chart.ts
@@ -0,0 +1,356 @@
+import { useChartHooks } from '@/views/knowledge/hooks/chart.ts'
+import { computed, ref } from 'vue'
+import { router } from "@/router/index.ts";
+import { createData } from './mockData'
+
+
+// 存储已生成的颜色,确保不重复
+const usedColors = new Set()
+
+/**
+ * 生成随机且不重复的颜色
+ * @returns 十六进制颜色值
+ */
+const generateRandomColor = (): string => {
+ // 生成随机颜色
+ const generateColor = (): string => {
+ // 生成更亮的颜色,避免太暗
+ const r = Math.floor(Math.random() * 155) + 100
+ const g = Math.floor(Math.random() * 155) + 100
+ const b = Math.floor(Math.random() * 155) + 100
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
+ }
+
+ let color: string
+ let attempts = 0
+
+ // 尝试生成不重复的颜色,最多尝试100次
+ do {
+ color = generateColor()
+ attempts++
+ } while (usedColors.has(color) && attempts < 100)
+
+ // 如果尝试次数过多,清空已使用的颜色重新开始
+ if (attempts >= 100) {
+ usedColors.clear()
+ color = generateColor()
+ }
+
+ usedColors.add(color)
+ return color
+}
+
+/**
+ * 为关系类型生成颜色映射
+ */
+const relationColors = ref