├── .stylelintignore ├── src ├── styles │ ├── index.less │ ├── tailwind.css │ └── transition │ │ ├── index.less │ │ ├── base.less │ │ ├── scale.less │ │ ├── zoom.less │ │ ├── slide.less │ │ ├── scroll.less │ │ └── fade.less ├── layout │ ├── parentLayout.vue │ └── components │ │ ├── Logo │ │ ├── index.ts │ │ └── index.vue │ │ ├── Main │ │ ├── index.ts │ │ └── index.vue │ │ ├── Menu │ │ └── index.ts │ │ ├── Footer │ │ ├── index.ts │ │ └── index.vue │ │ ├── Header │ │ ├── index.ts │ │ └── components.ts │ │ └── TagsView │ │ └── index.ts ├── hooks │ ├── index.ts │ ├── useAsync.ts │ ├── setting │ │ ├── useDesignSetting.ts │ │ ├── index.ts │ │ └── useProjectSetting.ts │ ├── useDomWidth.ts │ ├── useOnline.ts │ ├── event │ │ ├── useWindowSizeFn.ts │ │ ├── useEventListener.ts │ │ └── useBreakpoint.ts │ ├── core │ │ └── useTimeout.ts │ ├── web │ │ ├── usePermission.ts │ │ └── usePage.ts │ ├── useTime.ts │ └── useBattery.ts ├── components │ ├── Upload │ │ ├── index.ts │ │ └── src │ │ │ ├── type │ │ │ └── index.ts │ │ │ └── props.ts │ ├── Lockscreen │ │ └── index.ts │ ├── Application │ │ ├── index.ts │ │ └── Application.vue │ ├── Table │ │ ├── src │ │ │ ├── types │ │ │ │ ├── pagination.ts │ │ │ │ ├── componentType.ts │ │ │ │ ├── tableAction.ts │ │ │ │ └── table.ts │ │ │ ├── const.ts │ │ │ ├── components │ │ │ │ └── editable │ │ │ │ │ ├── helper.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── CellComponent.ts │ │ │ ├── hooks │ │ │ │ ├── useLoading.ts │ │ │ │ ├── useTableContext.ts │ │ │ │ └── usePagination.ts │ │ │ ├── componentMap.ts │ │ │ └── props.ts │ │ └── index.ts │ ├── CountTo │ │ └── index.ts │ ├── Modal │ │ ├── index.ts │ │ └── src │ │ │ ├── type │ │ │ └── index.ts │ │ │ ├── props.ts │ │ │ └── hooks │ │ │ └── useModal.ts │ └── Form │ │ ├── index.ts │ │ └── src │ │ ├── hooks │ │ ├── useFormContext.ts │ │ └── useFormValues.ts │ │ ├── types │ │ ├── index.ts │ │ └── form.ts │ │ ├── helper.ts │ │ └── props.ts ├── enums │ ├── permissionsEnum.ts │ ├── roleEnum.ts │ ├── pageEnum.ts │ ├── cacheEnum.ts │ ├── breakpointEnum.ts │ └── httpEnum.ts ├── assets │ └── images │ │ ├── logo.png │ │ ├── tool.png │ │ ├── schoolboy.png │ │ ├── account-logo.png │ │ ├── nav-horizontal-mix.svg │ │ └── nav-horizontal.svg ├── plugins │ ├── globalMethods.ts │ ├── customComponents.ts │ ├── index.ts │ ├── directives.ts │ └── naiveDiscreteApi.ts ├── api │ ├── dashboard │ │ └── console.ts │ ├── table │ │ └── list.ts │ ├── system │ │ ├── role.ts │ │ └── menu.ts │ ├── sysConfig.ts │ ├── knowledgeBase.ts │ ├── modelPlatform.ts │ ├── mcp.ts │ ├── aiModel.ts │ ├── conversation.ts │ ├── user.ts │ └── workflow.ts ├── router │ ├── icons.ts │ ├── constant.ts │ ├── modules │ │ ├── mcp.ts │ │ ├── user.ts │ │ ├── workflow.ts │ │ ├── aiModel.ts │ │ ├── conversation.ts │ │ ├── knowledgeBase.ts │ │ ├── dashboard.ts │ │ └── system.ts │ ├── base.ts │ ├── types.ts │ └── index.ts ├── store │ ├── index.ts │ ├── mutation-types.ts │ └── modules │ │ ├── screenLock.ts │ │ ├── designSetting.ts │ │ ├── tabsView.ts │ │ ├── projectSetting.ts │ │ └── user.ts ├── utils │ ├── log.ts │ ├── dateUtil.ts │ ├── lodashChunk.ts │ ├── urlUtils.ts │ ├── propTypes.ts │ ├── lib │ │ └── echarts.ts │ ├── http │ │ └── axios │ │ │ ├── checkStatus.ts │ │ │ ├── helper.ts │ │ │ ├── axiosTransform.ts │ │ │ ├── types.ts │ │ │ └── axiosCancel.ts │ ├── constants.ts │ ├── downloadFile.ts │ └── env.ts ├── settings │ ├── animateSetting.ts │ ├── designSetting.ts │ ├── componentSetting.ts │ └── projectSetting.ts ├── config │ └── website.config.ts ├── views │ ├── dashboard │ │ └── console │ │ │ └── components │ │ │ ├── props.ts │ │ │ ├── Icons.ts │ │ │ ├── VisiTab.vue │ │ │ └── VisitAmount.vue │ ├── redirect │ │ └── index.vue │ ├── conversation │ │ ├── preset-conv │ │ │ └── columns.ts │ │ └── columns.ts │ ├── system │ │ ├── storage │ │ │ ├── LocalConfig.vue │ │ │ └── index.vue │ │ ├── ratelimit │ │ │ ├── TextRequestRateLimit.vue │ │ │ └── ImageGenerateRateLimit.vue │ │ └── quota │ │ │ ├── KnowledgeBaseSetting.vue │ │ │ ├── RequestSetting.vue │ │ │ ├── TokenSetting.vue │ │ │ └── ImageGenerateSetting.vue │ ├── exception │ │ ├── 403.vue │ │ ├── 404.vue │ │ └── 500.vue │ ├── workflow │ │ ├── wf-component │ │ │ └── columns.ts │ │ └── columns.ts │ ├── ai-model │ │ ├── platformColumns.ts │ │ └── columns.ts │ ├── mcp │ │ └── columns.ts │ ├── user │ │ └── columns.ts │ └── knowledge-base │ │ └── columns.ts ├── directives │ ├── permission.ts │ ├── debounce.ts │ ├── copy.ts │ ├── throttle.ts │ ├── longpress.ts │ └── draggable.ts ├── main.ts └── App.vue ├── public └── favicon.ico ├── image └── README │ ├── model.png │ ├── user.png │ ├── console.png │ ├── workflow.png │ ├── aiplatform.png │ ├── sysconfig.png │ └── knowledgebase.png ├── postcss.config.js ├── .prettierignore ├── types ├── utils.d.ts ├── aiModel.d.ts ├── modules.d.ts ├── user.d.ts ├── conversation.d.ts ├── sysConfig.d.ts ├── index.d.ts ├── mcp.d.ts └── config.d.ts ├── tailwind.config.js ├── .env ├── .eslintignore ├── .editorconfig ├── .gitignore ├── prettier.config.js ├── .env.development ├── mock ├── _createProductionServer.ts ├── dashboard │ └── console.ts ├── user │ ├── menus.ts │ └── user.ts ├── system │ ├── role.ts │ └── menu.ts ├── _util.ts └── table │ └── list.ts ├── .env.production ├── nginx.conf ├── LICENSE ├── tsconfig.json ├── commitlint.config.js ├── stylelint.config.js ├── vite.config.ts └── .eslintrc.js /.stylelintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /public/* 3 | public/* 4 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import url('./transition/index.less'); 2 | -------------------------------------------------------------------------------- /src/layout/parentLayout.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { useAsync } from './useAsync'; 2 | 3 | export { useAsync }; 4 | -------------------------------------------------------------------------------- /src/components/Upload/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicUpload } from './src/BasicUpload.vue'; 2 | -------------------------------------------------------------------------------- /src/layout/components/Logo/index.ts: -------------------------------------------------------------------------------- 1 | import Logo from './index.vue'; 2 | 3 | export { Logo }; 4 | -------------------------------------------------------------------------------- /src/layout/components/Main/index.ts: -------------------------------------------------------------------------------- 1 | import MainView from './index.vue'; 2 | 3 | export { MainView }; 4 | -------------------------------------------------------------------------------- /src/layout/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | import AsideMenu from './index.vue'; 2 | 3 | export { AsideMenu }; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/components/Lockscreen/index.ts: -------------------------------------------------------------------------------- 1 | import LockScreen from './Lockscreen.vue'; 2 | 3 | export { LockScreen }; 4 | -------------------------------------------------------------------------------- /src/layout/components/Footer/index.ts: -------------------------------------------------------------------------------- 1 | import PageFooter from './index.vue'; 2 | 3 | export { PageFooter }; 4 | -------------------------------------------------------------------------------- /src/layout/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | import PageHeader from './index.vue'; 2 | 3 | export { PageHeader }; 4 | -------------------------------------------------------------------------------- /src/layout/components/TagsView/index.ts: -------------------------------------------------------------------------------- 1 | import TabsView from './index.vue'; 2 | 3 | export { TabsView }; 4 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | /*! @import */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | -------------------------------------------------------------------------------- /image/README/model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/image/README/model.png -------------------------------------------------------------------------------- /image/README/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/image/README/user.png -------------------------------------------------------------------------------- /src/components/Application/index.ts: -------------------------------------------------------------------------------- 1 | import AppProvider from './Application.vue'; 2 | 3 | export { AppProvider }; 4 | -------------------------------------------------------------------------------- /src/enums/permissionsEnum.ts: -------------------------------------------------------------------------------- 1 | export interface PermissionsEnum { 2 | value: string; 3 | label: string; 4 | } 5 | -------------------------------------------------------------------------------- /image/README/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/image/README/console.png -------------------------------------------------------------------------------- /image/README/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/image/README/workflow.png -------------------------------------------------------------------------------- /image/README/aiplatform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/image/README/aiplatform.png -------------------------------------------------------------------------------- /image/README/sysconfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/image/README/sysconfig.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/src/assets/images/tool.png -------------------------------------------------------------------------------- /src/plugins/globalMethods.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 注册全局方法 待完善 3 | * @param app 4 | */ 5 | export function setupGlobalMethods() {} 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | .output.js 4 | /node_modules/** 5 | 6 | **/*.svg 7 | **/*.sh 8 | 9 | /public/* 10 | -------------------------------------------------------------------------------- /image/README/knowledgebase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/image/README/knowledgebase.png -------------------------------------------------------------------------------- /src/assets/images/schoolboy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/src/assets/images/schoolboy.png -------------------------------------------------------------------------------- /src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | // 管理员 3 | ADMIN = 'admin', 4 | 5 | // 普通用户 6 | NORMAL = 'normal', 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/images/account-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/src/assets/images/account-logo.png -------------------------------------------------------------------------------- /src/components/Table/src/types/pagination.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moyangzhan/langchain4j-aideepin-admin/HEAD/src/components/Table/src/types/pagination.ts -------------------------------------------------------------------------------- /src/plugins/customComponents.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局注册自定义组件 待完善 3 | * @param app 4 | */ 5 | export function setupCustomComponents() { 6 | // app.component() 7 | } 8 | -------------------------------------------------------------------------------- /src/components/CountTo/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | import countTo from './CountTo.vue'; 3 | 4 | export const CountTo = withInstall(countTo); 5 | -------------------------------------------------------------------------------- /types/utils.d.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from 'vue'; 2 | 3 | export type DynamicProps = { 4 | [P in keyof T]: Ref | T[P] | ComputedRef; 5 | }; 6 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['./index.html', './src/**/*.{vue,ts,tsx}'], 3 | important: true, 4 | theme: { 5 | extend: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/Modal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as basicModal } from './src/basicModal.vue'; 2 | export { useModal } from './src/hooks/useModal'; 3 | export * from './src/type'; 4 | -------------------------------------------------------------------------------- /src/components/Upload/src/type/index.ts: -------------------------------------------------------------------------------- 1 | export interface BasicProps { 2 | title?: string; 3 | dataSource: Function; 4 | columns: any[]; 5 | pagination: object; 6 | showPagination: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # port 2 | VITE_PORT = 1003 3 | 4 | # spa-title 5 | VITE_GLOB_APP_TITLE = AdminPro 6 | 7 | # spa shortname 8 | VITE_GLOB_APP_SHORT_NAME = AdminPro 9 | 10 | # 生产环境 开启mock 11 | VITE_GLOB_PROD_MOCK = true 12 | -------------------------------------------------------------------------------- /src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicForm } from './src/BasicForm.vue'; 2 | export { useForm } from './src/hooks/useForm'; 3 | export * from './src/types/form'; 4 | export * from './src/types/index'; 5 | -------------------------------------------------------------------------------- /src/api/dashboard/console.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | //获取主控台信息 4 | export function getStatistic() { 5 | return http.request({ 6 | url: '/admin/statistic/info', 7 | method: 'get', 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /src/router/icons.ts: -------------------------------------------------------------------------------- 1 | import { renderIcon } from '@/utils/index'; 2 | import { DashboardOutlined } from '@vicons/antd'; 3 | 4 | //前端路由图标映射表 5 | export const constantRouterIcon = { 6 | DashboardOutlined: renderIcon(DashboardOutlined), 7 | }; 8 | -------------------------------------------------------------------------------- /src/api/table/list.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | //获取table 4 | export function getTableList(params) { 5 | return http.request({ 6 | url: '/table/list', 7 | method: 'get', 8 | params, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTable } from './src/Table.vue'; 2 | export { default as TableAction } from './src/components/TableAction.vue'; 3 | export * from './src/types/table'; 4 | export * from './src/types/tableAction'; 5 | -------------------------------------------------------------------------------- /src/components/Table/src/types/componentType.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'NInput' 3 | | 'NInputNumber' 4 | | 'NSelect' 5 | | 'NCheckbox' 6 | | 'NSwitch' 7 | | 'NDatePicker' 8 | | 'NTimePicker' 9 | | 'NCascader'; 10 | -------------------------------------------------------------------------------- /src/api/system/role.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | /** 4 | * @description: 角色列表 5 | */ 6 | export function getRoleList() { 7 | return http.request({ 8 | url: '/role/list', 9 | method: 'GET', 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | 4 | const store = createPinia(); 5 | 6 | export function setupStore(app: App) { 7 | app.use(store); 8 | } 9 | 10 | export { store }; 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | node_modules 3 | *.md 4 | *.woff 5 | *.ttf 6 | .vscode 7 | .idea 8 | dist 9 | /public 10 | /docs 11 | .husky 12 | .local 13 | /bin 14 | Dockerfile 15 | components.d.ts 16 | components.d.ts 17 | docker-compose 18 | kubernetes 19 | -------------------------------------------------------------------------------- /src/store/mutation-types.ts: -------------------------------------------------------------------------------- 1 | export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token 2 | export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息 3 | export const IS_SCREENLOCKED = 'IS-SCREENLOCKED'; // 是否锁屏 4 | export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页 5 | -------------------------------------------------------------------------------- /types/aiModel.d.ts: -------------------------------------------------------------------------------- 1 | export interface AiModelData { 2 | id: string 3 | type: string 4 | name: string 5 | platform: string 6 | remark: string 7 | createTime: string 8 | updateTime: string 9 | isEnable: boolean 10 | isFree: boolean 11 | setting: string 12 | } -------------------------------------------------------------------------------- /src/router/constant.ts: -------------------------------------------------------------------------------- 1 | export const RedirectName = 'Redirect'; 2 | 3 | export const ErrorPage = () => import('@/views/exception/404.vue'); 4 | 5 | export const Layout = () => import('@/layout/index.vue'); 6 | 7 | export const ParentLayout = () => import('@/layout/parentLayout.vue'); 8 | -------------------------------------------------------------------------------- /src/components/Form/src/hooks/useFormContext.ts: -------------------------------------------------------------------------------- 1 | import { provide, inject } from 'vue'; 2 | 3 | const key = Symbol('formElRef'); 4 | 5 | export function createFormContext(instance) { 6 | provide(key, instance); 7 | } 8 | 9 | export function useFormContext() { 10 | return inject(key); 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | const projectName = import.meta.env.VITE_GLOB_APP_TITLE; 2 | 3 | export function warn(message: string) { 4 | console.warn(`[${projectName} warn]:${message}`); 5 | } 6 | 7 | export function error(message: string) { 8 | throw new Error(`[${projectName} error]:${message}`); 9 | } 10 | -------------------------------------------------------------------------------- /src/settings/animateSetting.ts: -------------------------------------------------------------------------------- 1 | export const animates = [ 2 | { value: 'zoom-fade', label: '渐变' }, 3 | { value: 'zoom-out', label: '闪现' }, 4 | { value: 'fade-slide', label: '滑动' }, 5 | { value: 'fade', label: '消退' }, 6 | { value: 'fade-bottom', label: '底部消退' }, 7 | { value: 'fade-scale', label: '缩放消退' }, 8 | ]; 9 | -------------------------------------------------------------------------------- /src/config/website.config.ts: -------------------------------------------------------------------------------- 1 | import logoImage from '@/assets/images/logo.png'; 2 | import loginImage from '@/assets/images/account-logo.png'; 3 | 4 | export const websiteConfig = Object.freeze({ 5 | title: 'Aideepin Admin', 6 | logo: logoImage, 7 | loginImage: loginImage, 8 | loginDesc: 'Aideepin管理后台', 9 | }); 10 | -------------------------------------------------------------------------------- /types/modules.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.vue' { 3 | import { DefineComponent } from 'vue'; 4 | const Component: DefineComponent<{}, {}, any>; 5 | export default Component; 6 | } 7 | 8 | declare module 'virtual:*' { 9 | const result: any; 10 | export default result; 11 | } 12 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export { setupNaive } from '@/plugins/naive'; 2 | export { setupNaiveDiscreteApi } from '@/plugins/naiveDiscreteApi'; 3 | export { setupDirectives } from '@/plugins/directives'; 4 | export { setupCustomComponents } from '@/plugins/customComponents'; 5 | export { setupGlobalMethods } from '@/plugins/globalMethods'; 6 | -------------------------------------------------------------------------------- /src/styles/transition/index.less: -------------------------------------------------------------------------------- 1 | @import './base.less'; 2 | @import './fade.less'; 3 | @import './scale.less'; 4 | @import './slide.less'; 5 | @import './scroll.less'; 6 | @import './zoom.less'; 7 | 8 | .collapse-transition { 9 | transition: 0.2s height ease-in-out, 0.2s padding-top ease-in-out, 0.2s padding-bottom ease-in-out; 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset=utf-8 5 | end_of_line=LF 6 | insert_final_newline=true 7 | indent_style=space 8 | indent_size=2 9 | max_line_length = 100 10 | 11 | [*.{yml,yaml,json}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [Makefile] 19 | indent_style = tab -------------------------------------------------------------------------------- /src/components/Table/src/const.ts: -------------------------------------------------------------------------------- 1 | import componentSetting from '@/settings/componentSetting'; 2 | 3 | const { table } = componentSetting; 4 | 5 | const { apiSetting, defaultPageSize, pageSizes } = table; 6 | 7 | export const DEFAULTPAGESIZE = defaultPageSize; 8 | 9 | export const APISETTING = apiSetting; 10 | 11 | export const PAGESIZES = pageSizes; 12 | -------------------------------------------------------------------------------- /src/enums/pageEnum.ts: -------------------------------------------------------------------------------- 1 | export enum PageEnum { 2 | // 登录 3 | BASE_LOGIN = '/login', 4 | BASE_LOGIN_NAME = 'Login', 5 | //重定向 6 | REDIRECT = '/redirect', 7 | REDIRECT_NAME = 'Redirect', 8 | // 首页 9 | BASE_HOME = '/dashboard', 10 | //首页跳转默认路由 11 | BASE_HOME_REDIRECT = '/dashboard/console', 12 | // 错误 13 | ERROR_PAGE_NAME = 'ErrorPage', 14 | } 15 | -------------------------------------------------------------------------------- /types/user.d.ts: -------------------------------------------------------------------------------- 1 | export type UserInfoType = { 2 | uuid: string 3 | name: string 4 | email: string 5 | } 6 | export interface IUserState { 7 | token: string 8 | name: string 9 | welcome: string 10 | avatar: string 11 | permissions: any[] 12 | info: UserInfoType 13 | } 14 | export type UserListParams = { 15 | uuid: string 16 | name: string 17 | email: string 18 | } -------------------------------------------------------------------------------- /src/styles/transition/base.less: -------------------------------------------------------------------------------- 1 | .transition-default() { 2 | &-enter-active, 3 | &-leave-active { 4 | transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important; 5 | } 6 | 7 | &-move { 8 | transition: transform 0.4s; 9 | } 10 | } 11 | 12 | .expand-transition { 13 | .transition-default(); 14 | } 15 | 16 | .expand-x-transition { 17 | .transition-default(); 18 | } 19 | -------------------------------------------------------------------------------- /src/views/dashboard/console/components/props.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from 'vue'; 2 | 3 | export interface BasicProps { 4 | width: string; 5 | height: string; 6 | } 7 | 8 | export const basicProps = { 9 | width: { 10 | type: String as PropType, 11 | default: '100%', 12 | }, 13 | height: { 14 | type: String as PropType, 15 | default: '280px', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/styles/transition/scale.less: -------------------------------------------------------------------------------- 1 | .scale-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave, 6 | &-leave-to { 7 | opacity: 0; 8 | transform: scale(0); 9 | } 10 | } 11 | 12 | .scale-rotate-transition { 13 | .transition-default(); 14 | 15 | &-enter-from, 16 | &-leave, 17 | &-leave-to { 18 | opacity: 0; 19 | transform: scale(0) rotate(-45deg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /screenshots 4 | /dist 5 | dist.zip 6 | dist_electron 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | pnpm-debug.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | /components.d.ts 27 | /components.d.ts 28 | -------------------------------------------------------------------------------- /src/utils/dateUtil.ts: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns'; 2 | 3 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; 4 | const DATE_FORMAT = 'YYYY-MM-DD '; 5 | 6 | export function formatToDateTime(date: Date | number, formatStr = DATE_TIME_FORMAT): string { 7 | return format(date, formatStr); 8 | } 9 | 10 | export function formatToDate(date: Date | number, formatStr = DATE_FORMAT): string { 11 | return format(date, formatStr); 12 | } 13 | -------------------------------------------------------------------------------- /types/conversation.d.ts: -------------------------------------------------------------------------------- 1 | export interface Conversation { 2 | id: string 3 | uuid: string 4 | title: string 5 | aiSystemMessage: string 6 | tokens: number 7 | understandContextEnable: boolean 8 | createTime: string 9 | updateTime: string 10 | } 11 | 12 | export interface ConversationPreset { 13 | id: string 14 | uuid: string 15 | title: string 16 | remark: string 17 | createTime: string 18 | updateTime: string 19 | } -------------------------------------------------------------------------------- /src/api/system/menu.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | /** 4 | * @description: 根据用户id获取用户菜单 5 | */ 6 | export function adminMenus() { 7 | return http.request({ 8 | url: '/menus', 9 | method: 'GET', 10 | }); 11 | } 12 | 13 | /** 14 | * 获取tree菜单列表 15 | * @param params 16 | */ 17 | export function getMenuList(params?) { 18 | return http.request({ 19 | url: '/menu/list', 20 | method: 'GET', 21 | params, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/lodashChunk.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里按需引入lodash的一些方法,方便维护 3 | */ 4 | 5 | // export {default as xxx} from 'lodash/xxx' 6 | 7 | export { default as cloneDeep } from 'lodash/cloneDeep'; 8 | export { default as intersection } from 'lodash/intersection'; 9 | export { default as get } from 'lodash/get'; 10 | export { default as upperFirst } from 'lodash/upperFirst'; 11 | export { default as omit } from 'lodash/omit'; 12 | export { default as debounce } from 'lodash/debounce'; 13 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from '../../types/componentType'; 2 | 3 | /** 4 | * @description: 生成placeholder 5 | */ 6 | export function createPlaceholderMessage(component: ComponentType) { 7 | if (component === 'NInput') return '请输入'; 8 | if ( 9 | ['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes( 10 | component 11 | ) 12 | ) 13 | return '请选择'; 14 | return ''; 15 | } 16 | -------------------------------------------------------------------------------- /src/hooks/useAsync.ts: -------------------------------------------------------------------------------- 1 | import { isReactive, isRef } from 'vue'; 2 | 3 | function setLoading(loading, val) { 4 | if (loading != undefined && isRef(loading)) { 5 | loading.value = val; 6 | } else if (loading != undefined && isReactive(loading)) { 7 | loading.loading = val; 8 | } 9 | } 10 | 11 | export const useAsync = async (func: Promise, loading: any): Promise => { 12 | setLoading(loading, true); 13 | 14 | return await func.finally(() => setLoading(loading, false)); 15 | } 16 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | vueIndentScriptAndStyle: true, 7 | singleQuote: true, 8 | quoteProps: 'as-needed', 9 | bracketSpacing: true, 10 | trailingComma: 'es5', 11 | jsxBracketSameLine: false, 12 | jsxSingleQuote: false, 13 | arrowParens: 'always', 14 | insertPragma: false, 15 | requirePragma: false, 16 | proseWrap: 'never', 17 | htmlWhitespaceSensitivity: 'strict', 18 | endOfLine: 'auto', 19 | rangeStart: 0, 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/Modal/src/type/index.ts: -------------------------------------------------------------------------------- 1 | import type { DialogOptions } from 'naive-ui/lib/dialog'; 2 | /** 3 | * @description: 弹窗对外暴露的方法 4 | */ 5 | export interface ModalMethods { 6 | setProps: (props) => void; 7 | openModal: () => void; 8 | closeModal: () => void; 9 | setSubLoading: (status) => void; 10 | } 11 | 12 | /** 13 | * 支持修改,DialogOptions 參數 14 | */ 15 | export type ModalProps = DialogOptions; 16 | 17 | export type RegisterFn = (ModalInstance: ModalMethods) => void; 18 | 19 | export type UseModalReturnType = [RegisterFn, ModalMethods]; 20 | -------------------------------------------------------------------------------- /src/hooks/setting/useDesignSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useDesignSettingStore } from '@/store/modules/designSetting'; 3 | 4 | export function useDesignSetting() { 5 | const designStore = useDesignSettingStore(); 6 | 7 | const getDarkTheme = computed(() => designStore.darkTheme); 8 | 9 | const getAppTheme = computed(() => designStore.appTheme); 10 | 11 | const getAppThemeList = computed(() => designStore.appThemeList); 12 | 13 | return { 14 | getDarkTheme, 15 | getAppTheme, 16 | getAppThemeList, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/useDomWidth.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from 'vue'; 2 | import { debounce } from 'lodash-es'; 3 | 4 | /** 5 | * description: 获取页面宽度 6 | */ 7 | 8 | export function useDomWidth() { 9 | const domWidth = ref(window.innerWidth); 10 | 11 | function resize() { 12 | domWidth.value = document.body.clientWidth; 13 | } 14 | 15 | onMounted(() => { 16 | window.addEventListener('resize', debounce(resize, 80)); 17 | }) 18 | onUnmounted(() => { 19 | window.removeEventListener('resize', resize); 20 | }) 21 | 22 | return domWidth; 23 | } 24 | -------------------------------------------------------------------------------- /src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | // token key 2 | export const TOKEN_KEY = 'TOKEN'; 3 | 4 | // user info key 5 | export const USER_INFO_KEY = 'USER__INFO__'; 6 | 7 | // role info key 8 | export const ROLES_KEY = 'ROLES__KEY__'; 9 | 10 | // project config key 11 | export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'; 12 | 13 | // lock info 14 | export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'; 15 | 16 | // base global local key 17 | export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__'; 18 | 19 | // base global session key 20 | export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'; 21 | -------------------------------------------------------------------------------- /src/components/Modal/src/props.ts: -------------------------------------------------------------------------------- 1 | import { NModal } from 'naive-ui'; 2 | 3 | export const basicProps = { 4 | ...NModal.props, 5 | // 确认按钮文字 6 | subBtuText: { 7 | type: String, 8 | default: '确认', 9 | }, 10 | showIcon: { 11 | type: Boolean, 12 | default: false, 13 | }, 14 | width: { 15 | type: Number, 16 | default: 446, 17 | }, 18 | title: { 19 | type: String, 20 | default: '', 21 | }, 22 | maskClosable: { 23 | type: Boolean, 24 | default: false, 25 | }, 26 | preset: { 27 | type: String, 28 | default: 'dialog', 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/settings/designSetting.ts: -------------------------------------------------------------------------------- 1 | // app theme preset color 2 | export const appThemeList: string[] = [ 3 | '#2d8cf0', 4 | '#0960bd', 5 | '#0084f4', 6 | '#009688', 7 | '#536dfe', 8 | '#ff5c93', 9 | '#ee4f12', 10 | '#0096c7', 11 | '#9c27b0', 12 | '#ff9800', 13 | '#FF3D68', 14 | '#00C1D4', 15 | '#71EFA3', 16 | '#171010', 17 | '#78DEC7', 18 | '#1768AC', 19 | '#FB9300', 20 | '#FC5404', 21 | ]; 22 | 23 | const setting = { 24 | //深色主题 25 | darkTheme: false, 26 | //系统主题色 27 | appTheme: '#2d8cf0', 28 | //系统内置主题色列表 29 | appThemeList, 30 | }; 31 | 32 | export default setting; 33 | -------------------------------------------------------------------------------- /src/styles/transition/zoom.less: -------------------------------------------------------------------------------- 1 | // zoom-out 2 | .zoom-out-enter-active, 3 | .zoom-out-leave-active { 4 | transition: opacity 0.1 ease-in-out, transform 0.15s ease-out; 5 | } 6 | 7 | .zoom-out-enter-from, 8 | .zoom-out-leave-to { 9 | opacity: 0; 10 | transform: scale(0); 11 | } 12 | 13 | // zoom-fade 14 | .zoom-fade-enter-active, 15 | .zoom-fade-leave-active { 16 | transition: transform 0.2s, opacity 0.3s ease-out; 17 | } 18 | 19 | .zoom-fade-enter-from { 20 | opacity: 0; 21 | transform: scale(0.92); 22 | } 23 | 24 | .zoom-fade-leave-to { 25 | opacity: 0; 26 | transform: scale(1.06); 27 | } 28 | -------------------------------------------------------------------------------- /types/sysConfig.d.ts: -------------------------------------------------------------------------------- 1 | export interface QuotaConfig { 2 | daily: number 3 | monthly: number 4 | } 5 | export interface RateLimitConfig { 6 | times: number 7 | minutes: number 8 | } 9 | export interface AliyunOssConfig { 10 | access_key_id: string 11 | access_key_secret: string 12 | bucket_name: string 13 | endpoint: string 14 | } 15 | export interface AsrConfig { 16 | model_name: string 17 | platform: string 18 | max_record_duration: number 19 | max_file_size: number 20 | } 21 | export interface TtsConfig { 22 | synthesizer_side: string 23 | platform: string 24 | model_name: string 25 | } -------------------------------------------------------------------------------- /src/views/dashboard/console/components/Icons.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CaretUpOutlined, 3 | CaretDownOutlined, 4 | UsergroupAddOutlined, 5 | BarChartOutlined, 6 | ShoppingCartOutlined, 7 | AccountBookOutlined, 8 | CreditCardOutlined, 9 | MailOutlined, 10 | TagsOutlined, 11 | SettingOutlined, 12 | } from '@vicons/antd'; 13 | 14 | export default { 15 | CaretUpOutlined, 16 | CaretDownOutlined, 17 | UsergroupAddOutlined, 18 | BarChartOutlined, 19 | ShoppingCartOutlined, 20 | AccountBookOutlined, 21 | CreditCardOutlined, 22 | MailOutlined, 23 | TagsOutlined, 24 | SettingOutlined, 25 | }; 26 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 只在开发模式中被载入 2 | VITE_PORT = 1003 3 | 4 | # 资源公共路径,需要以 /开头和结尾 5 | VITE_PUBLIC_PATH = / 6 | 7 | # 是否开启mock 8 | VITE_USE_MOCK = false 9 | 10 | # 网站前缀 11 | VITE_BASE_URL = / 12 | 13 | # 是否删除console 14 | VITE_DROP_CONSOLE = true 15 | 16 | # 跨域代理,可以配置多个,请注意不要换行 17 | VITE_PROXY = [["/api","http://localhost:9999"]] 18 | #VITE_PROXY=[["/api","https://naive-ui-admin"]] 19 | 20 | # 接口地址 21 | # 如果没有跨域问题,直接在这里配置即可 22 | VITE_GLOB_API_URL = /api 23 | 24 | # 图片上传地址 25 | VITE_GLOB_UPLOAD_URL= 26 | 27 | # 图片前缀地址 28 | VITE_GLOB_IMG_URL= 29 | 30 | # 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换 31 | VITE_GLOB_API_URL_PREFIX = 32 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { ref, ComputedRef, unref, computed, watch } from 'vue'; 2 | import type { BasicTableProps } from '../types/table'; 3 | 4 | export function useLoading(props: ComputedRef) { 5 | const loadingRef = ref(unref(props).loading); 6 | 7 | watch( 8 | () => unref(props).loading, 9 | (loading) => { 10 | loadingRef.value = loading; 11 | } 12 | ); 13 | 14 | const getLoading = computed(() => unref(loadingRef)); 15 | 16 | function setLoading(loading: boolean) { 17 | loadingRef.value = loading; 18 | } 19 | 20 | return { getLoading, setLoading }; 21 | } 22 | -------------------------------------------------------------------------------- /mock/_createProductionServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; 2 | 3 | interface IModuleType { 4 | default: any[]; 5 | } 6 | 7 | const modules = import.meta.glob('./**/*.ts', { eager: true }); 8 | 9 | const mockModules: any[] = []; 10 | Object.keys(modules).forEach((key) => { 11 | if (key.includes('/_')) { 12 | return; 13 | } 14 | mockModules.push(...modules[key].default); 15 | }) 16 | 17 | /** 18 | * Used in a production environment. Need to manually import all modules 19 | */ 20 | export function setupProdMockServer() { 21 | createProdMockServer(mockModules); 22 | } 23 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 是否开启mock 2 | VITE_USE_MOCK = true 3 | 4 | # 资源公共路径,需要以 /开头和结尾 5 | VITE_PUBLIC_PATH = /admin/ 6 | 7 | # 网站前缀 8 | VITE_BASE_URL = /admin 9 | 10 | # 是否删除console 11 | VITE_DROP_CONSOLE = true 12 | 13 | # 接口地址 14 | # 如果没有跨域问题,直接在这里配置即可 15 | VITE_GLOB_API_URL = /api 16 | 17 | # 图片上传地址 18 | VITE_GLOB_UPLOAD_URL= 19 | 20 | # 图片前缀地址 21 | VITE_GLOB_IMG_URL= 22 | 23 | # 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换 24 | VITE_GLOB_API_URL_PREFIX = 25 | 26 | # 是否启用gzip压缩或brotli压缩 27 | # 可选: gzip | brotli | none 28 | # 如果你需要多种形式,你可以用','来分隔 29 | VITE_BUILD_COMPRESS = 'none' 30 | 31 | # 使用压缩时是否删除原始文件,默认为false 32 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false 33 | -------------------------------------------------------------------------------- /src/components/Form/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'NInput' 3 | | 'NInputGroup' 4 | | 'NInputPassword' 5 | | 'NInputSearch' 6 | | 'NInputTextArea' 7 | | 'NInputNumber' 8 | | 'NInputCountDown' 9 | | 'NSelect' 10 | | 'NTreeSelect' 11 | | 'NRadioButtonGroup' 12 | | 'NRadioGroup' 13 | | 'NCheckbox' 14 | | 'NCheckboxGroup' 15 | | 'NAutoComplete' 16 | | 'NCascader' 17 | | 'NDatePicker' 18 | | 'NMonthPicker' 19 | | 'NRangePicker' 20 | | 'NWeekPicker' 21 | | 'NTimePicker' 22 | | 'NSwitch' 23 | | 'NStrengthMeter' 24 | | 'NUpload' 25 | | 'NIconPicker' 26 | | 'NRender' 27 | | 'NSlider' 28 | | 'NRate'; 29 | -------------------------------------------------------------------------------- /src/directives/permission.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDirective } from 'vue'; 2 | import { usePermission } from '@/hooks/web/usePermission'; 3 | 4 | export const permission: ObjectDirective = { 5 | mounted(el: HTMLButtonElement, binding) { 6 | if (binding.value == undefined) return; 7 | const { action, effect } = binding.value; 8 | const { hasPermission } = usePermission(); 9 | if (!hasPermission(action)) { 10 | if (effect == 'disabled') { 11 | el.disabled = true; 12 | el.style['disabled'] = 'disabled'; 13 | el.classList.add('n-button--disabled'); 14 | } else { 15 | el.remove(); 16 | } 17 | } 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/enums/breakpointEnum.ts: -------------------------------------------------------------------------------- 1 | export enum sizeEnum { 2 | XS = 'XS', 3 | SM = 'SM', 4 | MD = 'MD', 5 | LG = 'LG', 6 | XL = 'XL', 7 | XXL = 'XXL', 8 | } 9 | 10 | export enum screenEnum { 11 | XS = 480, 12 | SM = 576, 13 | MD = 768, 14 | LG = 992, 15 | XL = 1200, 16 | XXL = 1600, 17 | } 18 | 19 | const screenMap = new Map(); 20 | 21 | screenMap.set(sizeEnum.XS, screenEnum.XS); 22 | screenMap.set(sizeEnum.SM, screenEnum.SM); 23 | screenMap.set(sizeEnum.MD, screenEnum.MD); 24 | screenMap.set(sizeEnum.LG, screenEnum.LG); 25 | screenMap.set(sizeEnum.XL, screenEnum.XL); 26 | screenMap.set(sizeEnum.XXL, screenEnum.XXL); 27 | 28 | export { screenMap }; 29 | -------------------------------------------------------------------------------- /src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/plugins/directives.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | 3 | import { permission } from '@/directives/permission'; 4 | import copy from '@/directives/copy'; 5 | import debounce from '@/directives/debounce'; 6 | import throttle from '@/directives/throttle'; 7 | import draggable from '@/directives/draggable'; 8 | 9 | /** 10 | * 注册全局自定义指令 11 | * @param app 12 | */ 13 | export function setupDirectives(app: App) { 14 | // 权限控制指令(演示) 15 | app.directive('permission', permission); 16 | // 复制指令 17 | app.directive('copy', copy); 18 | // 防抖指令 19 | app.directive('debounce', debounce); 20 | // 节流指令 21 | app.directive('throttle', throttle); 22 | // 拖拽指令 23 | app.directive('draggable', draggable); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useTableContext.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import type { BasicTableProps, TableActionType } from '../types/table'; 3 | import { provide, inject, ComputedRef } from 'vue'; 4 | 5 | const key = Symbol('s-table'); 6 | 7 | type Instance = TableActionType & { 8 | wrapRef: Ref>; 9 | getBindValues: ComputedRef; 10 | }; 11 | 12 | type RetInstance = Omit & { 13 | getBindValues: ComputedRef; 14 | }; 15 | 16 | export function createTableContext(instance: Instance) { 17 | provide(key, instance); 18 | } 19 | 20 | export function useTableContext(): RetInstance { 21 | return inject(key) as RetInstance; 22 | } 23 | -------------------------------------------------------------------------------- /src/layout/components/Header/components.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SettingOutlined, 3 | SearchOutlined, 4 | MenuFoldOutlined, 5 | MenuUnfoldOutlined, 6 | FullscreenOutlined, 7 | FullscreenExitOutlined, 8 | PoweroffOutlined, 9 | GithubOutlined, 10 | LockOutlined, 11 | ReloadOutlined, 12 | LogoutOutlined, 13 | UserOutlined, 14 | CheckOutlined, 15 | } from '@vicons/antd'; 16 | 17 | export default { 18 | SettingOutlined, 19 | LockOutlined, 20 | GithubOutlined, 21 | SearchOutlined, 22 | MenuFoldOutlined, 23 | MenuUnfoldOutlined, 24 | FullscreenOutlined, 25 | FullscreenExitOutlined, 26 | PoweroffOutlined, 27 | ReloadOutlined, 28 | LogoutOutlined, 29 | UserOutlined, 30 | CheckOutlined, 31 | }; 32 | -------------------------------------------------------------------------------- /src/views/conversation/preset-conv/columns.ts: -------------------------------------------------------------------------------- 1 | import { BasicColumn } from '@/components/Table' 2 | import { ConversationPreset } from '/#/conversation' 3 | export const columns: BasicColumn[] = [ 4 | { 5 | title: 'id', 6 | key: 'id', 7 | width: 50, 8 | }, 9 | { 10 | title: 'uuid', 11 | key: 'uuid', 12 | width: 120, 13 | }, 14 | { 15 | title: '标题', 16 | key: 'title', 17 | width: 100, 18 | }, 19 | { 20 | title: '描述', 21 | key: 'remark', 22 | width: 150, 23 | }, 24 | { 25 | title: '创建时间', 26 | key: 'createTime', 27 | width: 180, 28 | }, 29 | { 30 | title: '更新时间', 31 | key: 'updateTime', 32 | width: 180, 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /src/components/Application/Application.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 27 | -------------------------------------------------------------------------------- /src/utils/urlUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将对象添加当作参数拼接到URL上面 3 | * @param baseUrl 需要拼接的url 4 | * @param obj 参数对象 5 | * @returns {string} 拼接后的对象 6 | * 例子: 7 | * let obj = {a: '3', b: '4'} 8 | * setObjToUrlParams('www.baidu.com', obj) 9 | * ==>www.baidu.com?a=3&b=4 10 | */ 11 | export function setObjToUrlParams(baseUrl: string, obj: object): string { 12 | let parameters = ''; 13 | let url = ''; 14 | for (const key in obj) { 15 | parameters += key + '=' + encodeURIComponent(obj[key]) + '&'; 16 | } 17 | parameters = parameters.replace(/&$/, ''); 18 | if (/\?$/.test(baseUrl)) { 19 | url = baseUrl + parameters; 20 | } else { 21 | url = baseUrl.replace(/\/?$/, '?') + parameters; 22 | } 23 | return url; 24 | } 25 | -------------------------------------------------------------------------------- /src/styles/transition/slide.less: -------------------------------------------------------------------------------- 1 | .slide-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | transform: translateY(-15px); 8 | } 9 | } 10 | 11 | .slide-y-reverse-transition { 12 | .transition-default(); 13 | 14 | &-enter-from, 15 | &-leave-to { 16 | opacity: 0; 17 | transform: translateY(15px); 18 | } 19 | } 20 | 21 | .slide-x-transition { 22 | .transition-default(); 23 | 24 | &-enter-from, 25 | &-leave-to { 26 | opacity: 0; 27 | transform: translateX(-15px); 28 | } 29 | } 30 | 31 | .slide-x-reverse-transition { 32 | .transition-default(); 33 | 34 | &-enter-from, 35 | &-leave-to { 36 | opacity: 0; 37 | transform: translateX(15px); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/views/dashboard/console/components/VisiTab.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /src/api/sysConfig.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios' 2 | 3 | 4 | function search(data: {keyword?: string, names?: string[]}, currentPage: number, pageSize: number) { 5 | return http.request({ 6 | url: `/admin/sys-config/search?currentPage=${currentPage}&pageSize=${pageSize}`, 7 | method: 'post', 8 | data 9 | }) 10 | } 11 | 12 | function edit(params) { 13 | return http.request({ 14 | url: '/admin/sys-config/edit', 15 | method: 'post', 16 | params 17 | }) 18 | } 19 | 20 | function deleteOne(id: string) { 21 | return http.request( 22 | { 23 | url: `/admin/model/del/${id}`, 24 | method: 'POST', 25 | } 26 | ) 27 | } 28 | 29 | 30 | export default { 31 | search, 32 | edit, 33 | deleteOne, 34 | } 35 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 请求结果集 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 200, 6 | ERROR = -1, 7 | TIMEOUT = 10042, 8 | TYPE = 'success', 9 | } 10 | 11 | /** 12 | * @description: 请求方法 13 | */ 14 | export enum RequestEnum { 15 | GET = 'GET', 16 | POST = 'POST', 17 | PATCH = 'PATCH', 18 | PUT = 'PUT', 19 | DELETE = 'DELETE', 20 | } 21 | 22 | /** 23 | * @description: 常用的contentTyp类型 24 | */ 25 | export enum ContentTypeEnum { 26 | // json 27 | JSON = 'application/json;charset=UTF-8', 28 | // json 29 | TEXT = 'text/plain;charset=UTF-8', 30 | // form-data 一般配合qs 31 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 32 | // form-data 上传 33 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 34 | } 35 | -------------------------------------------------------------------------------- /src/hooks/useOnline.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from 'vue'; 2 | 3 | /** 4 | * @description 用户网络是否可用 5 | * */ 6 | export function useOnline() { 7 | const online = ref(true); 8 | 9 | const showStatus = (val) => { 10 | online.value = typeof val == 'boolean' ? val : val.target.online; 11 | } 12 | 13 | // 在页面加载后,设置正确的网络状态 14 | navigator.onLine ? showStatus(true) : showStatus(false); 15 | 16 | onMounted(() => { 17 | // 开始监听网络状态的变化 18 | window.addEventListener('online', showStatus); 19 | 20 | window.addEventListener('offline', showStatus); 21 | }) 22 | onUnmounted(() => { 23 | // 移除监听网络状态的变化 24 | window.removeEventListener('online', showStatus); 25 | 26 | window.removeEventListener('offline', showStatus); 27 | }) 28 | 29 | return { online }; 30 | } 31 | -------------------------------------------------------------------------------- /src/settings/componentSetting.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | table: { 3 | apiSetting: { 4 | // 当前页的字段名 5 | pageField: 'current', 6 | // 每页数量字段名 7 | sizeField: 'size', 8 | // 接口返回的数据字段名 9 | listField: 'records', 10 | // 接口返回总页数字段名 11 | totalField: 'pages', 12 | //总数字段名 13 | countField: 'total', 14 | }, 15 | //默认分页数量 16 | defaultPageSize: 10, 17 | //可切换每页数量集合 18 | pageSizes: [10, 20, 30, 40, 50], 19 | }, 20 | upload: { 21 | //考虑接口规范不同 22 | apiSetting: { 23 | // 集合字段名 24 | infoField: 'data', 25 | // 图片地址字段名 26 | imgField: 'photo', 27 | }, 28 | //最大上传图片大小 29 | maxSize: 2, 30 | //图片上传类型 31 | fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'], 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Fn { 2 | (...arg: T[]): R; 3 | } 4 | 5 | declare interface PromiseFn { 6 | (...arg: T[]): Promise; 7 | } 8 | 9 | declare type RefType = T | null; 10 | 11 | declare type LabelValueOptions = { 12 | label: string; 13 | value: any; 14 | disabled: boolean; 15 | [key: string]: string | number | boolean; 16 | }[]; 17 | 18 | declare type EmitType = (event: string, ...args: any[]) => void; 19 | 20 | declare type TargetContext = '_self' | '_blank'; 21 | 22 | declare interface ComponentElRef { 23 | $el: T; 24 | } 25 | 26 | declare type ComponentRef = ComponentElRef | null; 27 | 28 | declare type ElRef = Nullable; 29 | -------------------------------------------------------------------------------- /src/components/Upload/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue'; 2 | import { NUpload } from 'naive-ui'; 3 | 4 | export const basicProps = { 5 | ...NUpload.props, 6 | accept: { 7 | type: String, 8 | default: '.jpg,.png,.jpeg,.svg,.gif', 9 | }, 10 | helpText: { 11 | type: String as PropType, 12 | default: '', 13 | }, 14 | maxSize: { 15 | type: Number as PropType, 16 | default: 2, 17 | }, 18 | maxNumber: { 19 | type: Number as PropType, 20 | default: Infinity, 21 | }, 22 | value: { 23 | type: Array as PropType, 24 | default: () => [], 25 | }, 26 | width: { 27 | type: Number as PropType, 28 | default: 104, 29 | }, 30 | height: { 31 | type: Number as PropType, 32 | default: 104, //建议不小于这个尺寸 太小页面可能显示有异常 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/views/system/storage/LocalConfig.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 用户上传的文件(图片、文档等)存储到服务所在的服务器硬盘中 4 | 5 | 6 | 使用该存储位置 7 | 8 | -------------------------------------------------------------------------------- /src/utils/propTypes.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, VNodeChild } from 'vue'; 2 | import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; 3 | 4 | export type VueNode = VNodeChild | JSX.Element; 5 | 6 | type PropTypes = VueTypesInterface & { 7 | readonly style: VueTypeValidableDef; 8 | readonly VNodeChild: VueTypeValidableDef; 9 | }; 10 | 11 | const propTypes = createTypes({ 12 | func: undefined, 13 | bool: undefined, 14 | string: undefined, 15 | number: undefined, 16 | object: undefined, 17 | integer: undefined, 18 | }) as PropTypes; 19 | 20 | propTypes.extend([ 21 | { 22 | name: 'style', 23 | getter: true, 24 | type: [String, Object], 25 | default: undefined, 26 | }, 27 | { 28 | name: 'VNodeChild', 29 | getter: true, 30 | type: undefined, 31 | }, 32 | ]); 33 | export { propTypes }; 34 | -------------------------------------------------------------------------------- /src/components/Table/src/types/tableAction.ts: -------------------------------------------------------------------------------- 1 | import { NButton } from 'naive-ui'; 2 | import type { Component } from 'vue'; 3 | import { PermissionsEnum } from '@/enums/permissionsEnum'; 4 | export interface ActionItem extends Partial> { 5 | onClick?: Fn; 6 | label?: string; 7 | type?: 'success' | 'error' | 'warning' | 'info' | 'primary' | 'default'; 8 | // 设定 color 后会覆盖 type 的样式 9 | color?: string; 10 | icon?: Component; 11 | popConfirm?: PopConfirm; 12 | disabled?: boolean; 13 | divider?: boolean; 14 | // 权限编码控制是否显示 15 | auth?: PermissionsEnum | PermissionsEnum[] | string | string[]; 16 | // 业务控制是否显示 17 | ifShow?: boolean | ((action: ActionItem) => boolean); 18 | } 19 | 20 | export interface PopConfirm { 21 | title: string; 22 | okText?: string; 23 | cancelText?: string; 24 | confirm: Fn; 25 | cancel?: Fn; 26 | icon?: Component; 27 | } 28 | -------------------------------------------------------------------------------- /src/api/knowledgeBase.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios' 2 | 3 | function search(data, params: { current: number, size: number }) { 4 | return http.request({ 5 | url: `/admin/kb/search?currentPage=${params.current}&pageSize=${params.size}`, 6 | method: 'post', 7 | data, 8 | }) 9 | } 10 | 11 | function deleteOne(uuid: string) { 12 | return http.request( 13 | { 14 | url: `/admin/kb/del/${uuid}`, 15 | method: 'POST', 16 | } 17 | ) 18 | } 19 | 20 | function edit(data) { 21 | return http.request({ 22 | url: '/admin/kb/edit', 23 | method: 'post', 24 | data, 25 | }) 26 | } 27 | 28 | /** 29 | * @description: 获取用户信息 30 | */ 31 | function getInfo(uuid: string) { 32 | return http.request({ 33 | url: `/admin/kb/info/${uuid}`, 34 | method: 'get', 35 | }) 36 | } 37 | 38 | export default { 39 | search, 40 | getInfo, 41 | edit, 42 | deleteOne 43 | } 44 | -------------------------------------------------------------------------------- /src/directives/debounce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-debounce 3 | * 按钮防抖指令,可自行扩展至input 4 | * 接收参数:function类型 5 | */ 6 | import type { Directive, DirectiveBinding } from 'vue'; 7 | interface ElType extends HTMLElement { 8 | __handleClick__: () => any; 9 | } 10 | const debounce: Directive = { 11 | mounted(el: ElType, binding: DirectiveBinding) { 12 | if (typeof binding.value !== 'function') { 13 | throw 'callback must be a function'; 14 | } 15 | let timer: NodeJS.Timeout | null = null; 16 | el.__handleClick__ = function () { 17 | if (timer) { 18 | clearInterval(timer); 19 | } 20 | timer = setTimeout(() => { 21 | binding.value(); 22 | }, 500); 23 | } 24 | el.addEventListener('click', el.__handleClick__); 25 | }, 26 | beforeUnmount(el: ElType) { 27 | el.removeEventListener('click', el.__handleClick__); 28 | }, 29 | }; 30 | 31 | export default debounce; 32 | -------------------------------------------------------------------------------- /src/store/modules/screenLock.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { IS_SCREENLOCKED } from '@/store/mutation-types'; 3 | import { storage } from '@/utils/Storage'; 4 | 5 | // 长时间不操作默认锁屏时间 6 | const initTime = 60 * 60; 7 | 8 | const isLocked = storage.get(IS_SCREENLOCKED, false); 9 | 10 | export type IScreenLockState = { 11 | isLocked: boolean; // 是否锁屏 12 | lockTime: number; 13 | }; 14 | 15 | export const useScreenLockStore = defineStore({ 16 | id: 'app-screen-lock', 17 | state: (): IScreenLockState => ({ 18 | isLocked: isLocked === true, // 是否锁屏 19 | lockTime: isLocked == 'true' ? initTime : 0, 20 | }), 21 | getters: {}, 22 | actions: { 23 | setLock(payload: boolean) { 24 | this.isLocked = payload; 25 | storage.set(IS_SCREENLOCKED, this.isLocked); 26 | }, 27 | setLockTime(payload = initTime) { 28 | this.lockTime = payload; 29 | }, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /src/api/modelPlatform.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios' 2 | 3 | 4 | function search(data, params: { current: number, size: number }) { 5 | return http.request({ 6 | url: `/admin/model-platform/search?currentPage=${params.current}&pageSize=${params.size}`, 7 | method: 'post', 8 | data 9 | }) 10 | } 11 | 12 | function addOne(data) { 13 | return http.request({ 14 | url: '/admin/model-platform/add', 15 | method: 'post', 16 | data 17 | }) 18 | } 19 | 20 | function edit(data) { 21 | return http.request({ 22 | url: '/admin/model-platform/edit', 23 | method: 'post', 24 | data 25 | }) 26 | } 27 | 28 | function deleteOne(id: string) { 29 | return http.request( 30 | { 31 | url: `/admin/model-platform/del/${id}`, 32 | method: 'POST', 33 | } 34 | ) 35 | } 36 | 37 | 38 | export default { 39 | addOne, 40 | edit, 41 | search, 42 | deleteOne, 43 | } 44 | -------------------------------------------------------------------------------- /src/hooks/event/useWindowSizeFn.ts: -------------------------------------------------------------------------------- 1 | import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'; 2 | import { useDebounceFn } from '@vueuse/core'; 3 | 4 | interface WindowSizeOptions { 5 | once?: boolean; 6 | immediate?: boolean; 7 | listenerOptions?: AddEventListenerOptions | boolean; 8 | } 9 | 10 | export function useWindowSizeFn(fn: Fn, wait = 150, options?: WindowSizeOptions) { 11 | let handler = () => { 12 | fn(); 13 | } 14 | const handleSize = useDebounceFn(handler, wait); 15 | handler = handleSize; 16 | 17 | const start = () => { 18 | if (options && options.immediate) { 19 | handler(); 20 | } 21 | window.addEventListener('resize', handler); 22 | } 23 | 24 | const stop = () => { 25 | window.removeEventListener('resize', handler); 26 | } 27 | 28 | tryOnMounted(() => { 29 | start(); 30 | }) 31 | 32 | tryOnUnmounted(() => { 33 | stop(); 34 | }) 35 | return [start, stop]; 36 | } 37 | -------------------------------------------------------------------------------- /src/views/conversation/columns.ts: -------------------------------------------------------------------------------- 1 | import { BasicColumn } from '@/components/Table' 2 | import { Conversation } from '/#/conversation' 3 | export const columns: BasicColumn[] = [ 4 | { 5 | title: 'id', 6 | key: 'id', 7 | width: 50, 8 | }, 9 | { 10 | title: 'uuid', 11 | key: 'uuid', 12 | width: 120, 13 | }, 14 | { 15 | title: '标题', 16 | key: 'title', 17 | width: 150, 18 | }, 19 | { 20 | title: '角色描述', 21 | key: 'aiSystemMessage', 22 | width: 200, 23 | }, 24 | { 25 | title: '消耗的总token', 26 | key: 'tokens', 27 | width: 150, 28 | }, 29 | { 30 | title: '开启上下文', 31 | key: 'understandContextEnable', 32 | width: 100, 33 | render(row) { 34 | return row.understandContextEnable ? '是' : '否' 35 | }, 36 | }, 37 | { 38 | title: '创建时间', 39 | key: 'createTime', 40 | width: 180, 41 | }, 42 | { 43 | title: '更新时间', 44 | key: 'updateTime', 45 | width: 180, 46 | }, 47 | ] 48 | -------------------------------------------------------------------------------- /src/views/exception/403.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 抱歉,你无权访问该页面 8 | 回到首页 9 | 10 | 11 | 12 | 13 | 20 | 21 | 41 | -------------------------------------------------------------------------------- /src/views/exception/404.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 抱歉,你访问的页面不存在 8 | 回到首页 9 | 10 | 11 | 12 | 13 | 20 | 21 | 41 | -------------------------------------------------------------------------------- /src/views/exception/500.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 抱歉,服务器出错了 8 | 回到首页 9 | 10 | 11 | 12 | 13 | 20 | 21 | 41 | -------------------------------------------------------------------------------- /src/layout/components/Logo/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ websiteConfig.title }} 5 | 6 | 7 | 8 | 24 | 25 | 45 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | charset utf-8; 5 | proxy_buffering off; 6 | error_page 500 502 503 504 /50x.html; 7 | 8 | # 用户端WEB页面配置 9 | # 如果用户端WEB跟管理后台WEB不在同一台服务器上,注释本Location的配置 10 | # 访问地址:http://你的ip:port/ 11 | location / { 12 | root /usr/share/nginx/adi-web; 13 | index /index.html; 14 | } 15 | 16 | # 管理后台WEB页面配置 17 | # 访问地址:http://你的ip:port/admin 18 | # location中的 /admin 需要与.env.production中的VITE_PUBLIC_PATH设置的值相等 19 | location /admin/ { 20 | alias /data/adi-admin-web/; 21 | index /index.html; 22 | # 路由使用history的方式时需要用try_files指定默认页面 23 | # hash方式可以屏蔽该配置 24 | # try_files $uri $uri/ /admin/index.html; 25 | } 26 | 27 | # 后端服务 28 | location /api/ { 29 | proxy_set_header X-Real-IP $remote_addr; #转发用户IP 30 | proxy_pass http://localhost:9999/; 31 | } 32 | 33 | proxy_set_header Host $host; 34 | proxy_set_header X-Real-IP $remote_addr; 35 | proxy_set_header REMOTE-HOST $remote_addr; 36 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 37 | } -------------------------------------------------------------------------------- /src/store/modules/designSetting.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { store } from '@/store'; 3 | import designSetting from '@/settings/designSetting'; 4 | 5 | const { darkTheme, appTheme, appThemeList } = designSetting; 6 | 7 | interface DesignSettingState { 8 | //深色主题 9 | darkTheme: boolean; 10 | //系统风格 11 | appTheme: string; 12 | //系统内置风格 13 | appThemeList: string[]; 14 | } 15 | 16 | export const useDesignSettingStore = defineStore({ 17 | id: 'app-design-setting', 18 | state: (): DesignSettingState => ({ 19 | darkTheme, 20 | appTheme, 21 | appThemeList, 22 | }), 23 | getters: { 24 | getDarkTheme(): boolean { 25 | return this.darkTheme; 26 | }, 27 | getAppTheme(): string { 28 | return this.appTheme; 29 | }, 30 | getAppThemeList(): string[] { 31 | return this.appThemeList; 32 | }, 33 | }, 34 | actions: {}, 35 | }); 36 | 37 | // Need to be used outside the setup 38 | export function useDesignSetting() { 39 | return useDesignSettingStore(store); 40 | } 41 | -------------------------------------------------------------------------------- /src/directives/copy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-copy 3 | * 复制某个值至剪贴板 4 | * 接收参数:string类型/Ref类型/Reactive类型 5 | */ 6 | import type { Directive, DirectiveBinding } from 'vue'; 7 | 8 | interface ElType extends HTMLElement { 9 | copyData: string | number; 10 | __handleClick__: any; 11 | } 12 | const copy: Directive = { 13 | mounted(el: ElType, binding: DirectiveBinding) { 14 | el.copyData = binding.value; 15 | el.addEventListener('click', handleClick); 16 | }, 17 | updated(el: ElType, binding: DirectiveBinding) { 18 | el.copyData = binding.value; 19 | }, 20 | beforeUnmount(el: ElType) { 21 | el.removeEventListener('click', el.__handleClick__); 22 | }, 23 | }; 24 | function handleClick(this: any) { 25 | const input = document.createElement('input'); 26 | input.value = this.copyData.toLocaleString(); 27 | document.body.appendChild(input); 28 | input.select(); 29 | document.execCommand('Copy'); 30 | document.body.removeChild(input); 31 | console.log('复制成功', this.copyData); 32 | } 33 | 34 | export default copy; 35 | -------------------------------------------------------------------------------- /src/components/Form/src/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from './types/index'; 2 | 3 | /** 4 | * @description: 生成placeholder 5 | */ 6 | export function createPlaceholderMessage(component: ComponentType) { 7 | if (component === 'NInput') return '请输入'; 8 | if ( 9 | ['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes( 10 | component 11 | ) 12 | ) 13 | return '请选择'; 14 | return ''; 15 | } 16 | 17 | const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker']; 18 | 19 | function genType() { 20 | return [...DATE_TYPE, 'RangePicker']; 21 | } 22 | 23 | /** 24 | * 时间字段 25 | */ 26 | export const dateItemType = genType(); 27 | 28 | export function defaultType(component) { 29 | if (component === 'NInput') return ''; 30 | if (component === 'NInputNumber') return null; 31 | return [ 32 | 'NPicker', 33 | 'NSelect', 34 | 'NCheckbox', 35 | 'NRadio', 36 | 'NSwitch', 37 | 'NDatePicker', 38 | 'NTimePicker', 39 | ].includes(component) 40 | ? '' 41 | : undefined; 42 | } 43 | -------------------------------------------------------------------------------- /src/router/modules/mcp.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { Tools } from '@vicons/carbon'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | { 19 | path: '/mcp', 20 | name: 'Mcp', 21 | redirect: '/mcp/servers', 22 | component: Layout, 23 | meta: { 24 | title: 'MCP管理', 25 | icon: renderIcon(Tools), 26 | sort: 5, 27 | }, 28 | children: [ 29 | { 30 | path: 'servers', 31 | name: 'McpServers', 32 | meta: { 33 | title: 'MCP管理', 34 | }, 35 | component: () => import('@/views/mcp/index.vue'), 36 | }, 37 | ], 38 | }, 39 | ]; 40 | 41 | export default routes; 42 | -------------------------------------------------------------------------------- /src/router/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { UserOutlined } from '@vicons/antd'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | { 19 | path: '/user', 20 | name: 'User', 21 | redirect: '/user/list', 22 | component: Layout, 23 | meta: { 24 | title: '用户管理', 25 | icon: renderIcon(UserOutlined), 26 | sort: 1, 27 | }, 28 | children: [ 29 | { 30 | path: 'list', 31 | name: 'UserList', 32 | meta: { 33 | title: '用户管理', 34 | }, 35 | component: () => import('@/views/user/index.vue'), 36 | } 37 | ], 38 | }, 39 | ]; 40 | 41 | export default routes; 42 | -------------------------------------------------------------------------------- /src/views/workflow/wf-component/columns.ts: -------------------------------------------------------------------------------- 1 | import { BasicColumn } from '@/components/Table' 2 | export interface ComponentData { 3 | id: string 4 | uuid: string 5 | name: string 6 | title: string 7 | remark: string 8 | createTime: string 9 | updateTime: string 10 | isEnable: boolean 11 | } 12 | export const columns: BasicColumn[] = [ 13 | { 14 | title: 'id', 15 | key: 'id', 16 | width: 50, 17 | }, 18 | { 19 | title: '名称', 20 | key: 'name', 21 | width: 130, 22 | }, 23 | { 24 | title: '标题', 25 | key: 'title', 26 | width: 100, 27 | }, 28 | { 29 | title: '描述', 30 | key: 'remark', 31 | width: 260, 32 | }, 33 | { 34 | title: '排列顺序', 35 | key: 'displayOrder', 36 | width: 80, 37 | }, 38 | { 39 | title: '创建时间', 40 | key: 'createTime', 41 | width: 180, 42 | }, 43 | { 44 | title: '更新时间', 45 | key: 'updateTime', 46 | width: 180, 47 | }, 48 | { 49 | title: '是否启用', 50 | key: 'isEnable', 51 | width: 100, 52 | render(row) { 53 | return row.isEnable ? '是' : '否' 54 | }, 55 | }, 56 | ] 57 | -------------------------------------------------------------------------------- /src/router/base.ts: -------------------------------------------------------------------------------- 1 | import { ErrorPage, RedirectName, Layout } from '@/router/constant'; 2 | import { RouteRecordRaw } from 'vue-router'; 3 | 4 | // 404 on a page 5 | export const ErrorPageRoute: RouteRecordRaw = { 6 | path: '/:path(.*)*', 7 | name: 'ErrorPage', 8 | component: Layout, 9 | meta: { 10 | title: 'ErrorPage', 11 | hideBreadcrumb: true, 12 | }, 13 | children: [ 14 | { 15 | path: '/:path(.*)*', 16 | name: 'ErrorPageSon', 17 | component: ErrorPage, 18 | meta: { 19 | title: 'ErrorPage', 20 | hideBreadcrumb: true, 21 | }, 22 | }, 23 | ], 24 | }; 25 | 26 | export const RedirectRoute: RouteRecordRaw = { 27 | path: '/redirect', 28 | name: RedirectName, 29 | component: Layout, 30 | meta: { 31 | title: RedirectName, 32 | hideBreadcrumb: true, 33 | }, 34 | children: [ 35 | { 36 | path: '/redirect/:path(.*)', 37 | name: RedirectName, 38 | component: () => import('@/views/redirect/index.vue'), 39 | meta: { 40 | title: RedirectName, 41 | hideBreadcrumb: true, 42 | }, 43 | }, 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /src/views/ai-model/platformColumns.ts: -------------------------------------------------------------------------------- 1 | import { BasicColumn } from '@/components/Table' 2 | 3 | export interface AiPlatformData { 4 | id: string 5 | name: string 6 | title: string 7 | baseUrl: string 8 | apiKey: string 9 | secretKey: string 10 | isProxyEnable: boolean 11 | isOpenaiApiCompatible: boolean 12 | createTime: string 13 | updateTime: string 14 | } 15 | export const columns: BasicColumn[] = [ 16 | { 17 | title: '名称', 18 | key: 'name', 19 | width: 120, 20 | }, 21 | { 22 | title: '标题', 23 | key: 'title', 24 | }, 25 | { 26 | title: '接口地址(base_url)', 27 | key: 'baseUrl', 28 | }, 29 | { 30 | title: 'Api key', 31 | key: 'apiKey', 32 | }, 33 | { 34 | title: 'Secret key', 35 | key: 'secretKey', 36 | }, 37 | { 38 | title: '启用代理', 39 | key: 'isProxyEnable', 40 | render(row) { 41 | return row.isProxyEnable ? '启用' : '禁用' 42 | }, 43 | }, 44 | { 45 | title: '兼容OpenAI Api格式', 46 | key: 'isOpenaiApiCompatible', 47 | render(row) { 48 | return row.isOpenaiApiCompatible ? '是' : '否' 49 | }, 50 | }, 51 | ] 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 moyangzhan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/hooks/setting/index.ts: -------------------------------------------------------------------------------- 1 | import type { GlobConfig } from '/#/config'; 2 | 3 | import { warn } from '@/utils/log'; 4 | import { getAppEnvConfig } from '@/utils/env'; 5 | 6 | export const useGlobSetting = (): Readonly => { 7 | const { 8 | VITE_GLOB_APP_TITLE, 9 | VITE_GLOB_API_URL, 10 | VITE_GLOB_APP_SHORT_NAME, 11 | VITE_GLOB_API_URL_PREFIX, 12 | VITE_GLOB_UPLOAD_URL, 13 | VITE_GLOB_PROD_MOCK, 14 | VITE_GLOB_IMG_URL, 15 | } = getAppEnvConfig(); 16 | 17 | if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { 18 | warn( 19 | 'VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.' 20 | ); 21 | } 22 | 23 | // Take global configuration 24 | const glob: Readonly = { 25 | title: VITE_GLOB_APP_TITLE, 26 | apiUrl: VITE_GLOB_API_URL, 27 | shortName: VITE_GLOB_APP_SHORT_NAME, 28 | urlPrefix: VITE_GLOB_API_URL_PREFIX, 29 | uploadUrl: VITE_GLOB_UPLOAD_URL, 30 | prodMock: VITE_GLOB_PROD_MOCK, 31 | imgUrl: VITE_GLOB_IMG_URL, 32 | }; 33 | return glob as Readonly; 34 | } 35 | -------------------------------------------------------------------------------- /src/hooks/core/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch } from 'vue'; 2 | import { tryOnUnmounted } from '@vueuse/core'; 3 | import { isFunction } from '@/utils/is'; 4 | 5 | export function useTimeoutFn(handle: Fn, wait: number, native = false) { 6 | if (!isFunction(handle)) { 7 | throw new Error('handle is not Function!'); 8 | } 9 | 10 | const { readyRef, stop, start } = useTimeoutRef(wait); 11 | if (native) { 12 | handle(); 13 | } else { 14 | watch( 15 | readyRef, 16 | (maturity) => { 17 | maturity && handle(); 18 | }, 19 | { immediate: false } 20 | ); 21 | } 22 | return { readyRef, stop, start }; 23 | } 24 | 25 | export function useTimeoutRef(wait: number) { 26 | const readyRef = ref(false); 27 | 28 | let timer: TimeoutHandle; 29 | 30 | function stop(): void { 31 | readyRef.value = false; 32 | timer && window.clearTimeout(timer); 33 | } 34 | 35 | function start(): void { 36 | stop(); 37 | timer = setTimeout(() => { 38 | readyRef.value = true; 39 | }, wait); 40 | } 41 | 42 | start(); 43 | 44 | tryOnUnmounted(stop); 45 | 46 | return { readyRef, stop, start }; 47 | } 48 | -------------------------------------------------------------------------------- /src/views/mcp/columns.ts: -------------------------------------------------------------------------------- 1 | import { BasicColumn } from '@/components/Table' 2 | import { mcpInstallType, mcpTransportType } from '@/utils/constants' 3 | import { McpInfo } from '/#/mcp' 4 | 5 | export const columns: BasicColumn[] = [ 6 | { 7 | title: 'id', 8 | key: 'id', 9 | width: 50, 10 | }, 11 | { 12 | title: '标题', 13 | key: 'title', 14 | width: 100, 15 | }, 16 | { 17 | title: '传输类型', 18 | key: 'transportType', 19 | width: 100, 20 | render(row) { 21 | return mcpTransportType.find((item) => item.value === row.transportType)?.label 22 | }, 23 | }, 24 | { 25 | title: '安装类型', 26 | key: 'installType', 27 | width: 100, 28 | render(row) { 29 | return mcpInstallType.find((item) => item.value === row.installType)?.label 30 | }, 31 | }, 32 | { 33 | title: '是否启用', 34 | key: 'isEnable', 35 | width: 100, 36 | render(row) { 37 | return row.isEnable ? '是' : '否' 38 | }, 39 | }, 40 | { 41 | title: '创建时间', 42 | key: 'createTime', 43 | width: 180, 44 | }, 45 | { 46 | title: '更新时间', 47 | key: 'updateTime', 48 | width: 180, 49 | }, 50 | 51 | ] 52 | -------------------------------------------------------------------------------- /src/api/mcp.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios' 2 | import { McpSearchReq, McpAddOrEditReq } from '/#/mcp' 3 | 4 | function mcpSearch(data: McpSearchReq, params: { current: number, size: number }) { 5 | return http.request({ 6 | url: `/admin/mcp/search?currentPage=${params.current}&pageSize=${params.size}`, 7 | method: 'post', 8 | data, 9 | }) 10 | } 11 | 12 | function mcpAdd(data: McpAddOrEditReq) { 13 | return http.request({ 14 | url: '/admin/mcp/add', 15 | method: 'post', 16 | data 17 | }) 18 | } 19 | 20 | function mcpEdit(data: McpAddOrEditReq) { 21 | return http.request({ 22 | url: '/admin/mcp/edit', 23 | method: 'post', 24 | data 25 | }) 26 | } 27 | 28 | function mcpDel(uuid: string) { 29 | return http.request({ 30 | url: `/admin/mcp/del/${uuid}`, 31 | method: 'post', 32 | }) 33 | } 34 | 35 | function mcpSetEnable(params: { uuid: string; isEnable: boolean }) { 36 | return http.request({ 37 | url: `/admin/mcp/enable?uuid=${params.uuid}&isEnable=${params.isEnable}`, 38 | method: 'post', 39 | }) 40 | } 41 | 42 | export default { 43 | mcpSearch, 44 | mcpAdd, 45 | mcpEdit, 46 | mcpDel, 47 | mcpSetEnable 48 | } -------------------------------------------------------------------------------- /src/styles/transition/scroll.less: -------------------------------------------------------------------------------- 1 | .scroll-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | } 8 | 9 | &-enter-from { 10 | transform: translateY(-15px); 11 | } 12 | 13 | &-leave-to { 14 | transform: translateY(15px); 15 | } 16 | } 17 | 18 | .scroll-y-reverse-transition { 19 | .transition-default(); 20 | 21 | &-enter-from, 22 | &-leave-to { 23 | opacity: 0; 24 | } 25 | 26 | &-enter-from { 27 | transform: translateY(15px); 28 | } 29 | 30 | &-leave-to { 31 | transform: translateY(-15px); 32 | } 33 | } 34 | 35 | .scroll-x-transition { 36 | .transition-default(); 37 | 38 | &-enter-from, 39 | &-leave-to { 40 | opacity: 0; 41 | } 42 | 43 | &-enter-from { 44 | transform: translateX(-15px); 45 | } 46 | 47 | &-leave-to { 48 | transform: translateX(15px); 49 | } 50 | } 51 | 52 | .scroll-x-reverse-transition { 53 | .transition-default(); 54 | 55 | &-enter-from, 56 | &-leave-to { 57 | opacity: 0; 58 | } 59 | 60 | &-enter-from { 61 | transform: translateX(15px); 62 | } 63 | 64 | &-leave-to { 65 | transform: translateX(-15px); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/views/workflow/columns.ts: -------------------------------------------------------------------------------- 1 | import { BasicColumn } from '@/components/Table' 2 | export interface Data { 3 | id: string 4 | uuid: string 5 | title: string 6 | remark: string 7 | isPublic: boolean 8 | isEnable: boolean 9 | createTime: string 10 | updateTime: string 11 | } 12 | export const columns: BasicColumn[] = [ 13 | { 14 | title: 'id', 15 | key: 'id', 16 | width: 50, 17 | }, 18 | { 19 | title: '标题', 20 | key: 'title', 21 | width: 100, 22 | }, 23 | { 24 | title: '备注', 25 | key: 'remark', 26 | width: 150, 27 | }, 28 | { 29 | title: '是否公开', 30 | key: 'isPublic', 31 | width: 100, 32 | render(row) { 33 | return row.isPublic ? '是' : '否' 34 | }, 35 | }, 36 | { 37 | title: '是否启用', 38 | key: 'isEnable', 39 | width: 100, 40 | render(row) { 41 | return row.isEnable ? '是' : '否' 42 | }, 43 | }, 44 | { 45 | title: '创建用户', 46 | key: 'userName', 47 | width: 120, 48 | }, 49 | { 50 | title: '创建时间', 51 | key: 'createTime', 52 | width: 180, 53 | }, 54 | { 55 | title: '更新时间', 56 | key: 'updateTime', 57 | width: 180, 58 | }, 59 | 60 | ] 61 | -------------------------------------------------------------------------------- /src/utils/lib/echarts.ts: -------------------------------------------------------------------------------- 1 | import * as echarts from 'echarts/core'; 2 | 3 | import { 4 | BarChart, 5 | LineChart, 6 | PieChart, 7 | MapChart, 8 | PictorialBarChart, 9 | RadarChart, 10 | } from 'echarts/charts'; 11 | 12 | import { 13 | TitleComponent, 14 | TooltipComponent, 15 | GridComponent, 16 | PolarComponent, 17 | AriaComponent, 18 | ParallelComponent, 19 | LegendComponent, 20 | RadarComponent, 21 | ToolboxComponent, 22 | DataZoomComponent, 23 | VisualMapComponent, 24 | TimelineComponent, 25 | CalendarComponent, 26 | GraphicComponent, 27 | } from 'echarts/components'; 28 | 29 | import { SVGRenderer } from 'echarts/renderers'; 30 | 31 | echarts.use([ 32 | LegendComponent, 33 | TitleComponent, 34 | TooltipComponent, 35 | GridComponent, 36 | PolarComponent, 37 | AriaComponent, 38 | ParallelComponent, 39 | BarChart, 40 | LineChart, 41 | PieChart, 42 | MapChart, 43 | RadarChart, 44 | SVGRenderer, 45 | PictorialBarChart, 46 | RadarComponent, 47 | ToolboxComponent, 48 | DataZoomComponent, 49 | VisualMapComponent, 50 | TimelineComponent, 51 | CalendarComponent, 52 | GraphicComponent, 53 | ]); 54 | 55 | export default echarts; 56 | -------------------------------------------------------------------------------- /src/directives/throttle.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。 3 | 4 | 思路: 5 | 1、第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮 6 | 2、将需要触发的方法绑定在指令上 7 | 8 | 使用:给 Dom 加上 v-throttle 及回调函数即可 9 | 节流提交 10 | */ 11 | import type { Directive, DirectiveBinding } from 'vue'; 12 | interface ElType extends HTMLElement { 13 | __handleClick__: () => any; 14 | disabled: boolean; 15 | } 16 | const throttle: Directive = { 17 | mounted(el: ElType, binding: DirectiveBinding) { 18 | if (typeof binding.value !== 'function') { 19 | throw 'callback must be a function'; 20 | } 21 | let timer: NodeJS.Timeout | null = null; 22 | el.__handleClick__ = function () { 23 | if (timer) { 24 | clearTimeout(timer); 25 | } 26 | if (!el.disabled) { 27 | el.disabled = true; 28 | binding.value(); 29 | timer = setTimeout(() => { 30 | el.disabled = false; 31 | }, 1000); 32 | } 33 | }; 34 | el.addEventListener('click', el.__handleClick__); 35 | }, 36 | beforeUnmount(el: ElType) { 37 | el.removeEventListener('click', el.__handleClick__); 38 | }, 39 | }; 40 | 41 | export default throttle; 42 | -------------------------------------------------------------------------------- /src/settings/projectSetting.ts: -------------------------------------------------------------------------------- 1 | const setting = { 2 | //导航模式 vertical 左侧菜单模式 horizontal 顶部菜单模式 3 | navMode: 'vertical', 4 | //导航风格 dark 暗色侧边栏 light 白色侧边栏 header-dark 暗色顶栏 5 | navTheme: 'dark', 6 | // 是否处于移动端模式 7 | isMobile: false, 8 | //顶部 9 | headerSetting: { 10 | //背景色 11 | bgColor: '#fff', 12 | //固定顶部 13 | fixed: true, 14 | //显示重载按钮 15 | isReload: true, 16 | }, 17 | //页脚 18 | showFooter: true, 19 | //多标签 20 | multiTabsSetting: { 21 | //背景色 22 | bgColor: '#fff', 23 | //是否显示 24 | show: true, 25 | //固定多标签 26 | fixed: true, 27 | }, 28 | //菜单 29 | menuSetting: { 30 | //最小宽度 31 | minMenuWidth: 64, 32 | //菜单宽度 33 | menuWidth: 200, 34 | //固定菜单 35 | fixed: true, 36 | //分割菜单 37 | mixMenu: false, 38 | //触发移动端侧边栏的宽度 39 | mobileWidth: 800, 40 | // 折叠菜单 41 | collapsed: false, 42 | }, 43 | //面包屑 44 | crumbsSetting: { 45 | //是否显示 46 | show: true, 47 | //显示图标 48 | showIcon: false, 49 | }, 50 | //菜单权限模式 FIXED 前端固定路由 BACK 动态获取 51 | permissionMode: 'FIXED', 52 | //是否开启路由动画 53 | isPageAnimate: true, 54 | //路由动画类型 55 | pageAnimateType: 'zoom-fade', 56 | }; 57 | export default setting; 58 | -------------------------------------------------------------------------------- /mock/dashboard/console.ts: -------------------------------------------------------------------------------- 1 | import { Random } from 'mockjs'; 2 | import { resultSuccess } from '../_util'; 3 | 4 | const consoleInfo = { 5 | //访问量 6 | visits: { 7 | dayVisits: Random.float(10000, 99999, 2, 2), 8 | rise: Random.float(10, 99), 9 | decline: Random.float(10, 99), 10 | amount: Random.float(99999, 999999, 3, 5), 11 | }, 12 | //销售额 13 | saleroom: { 14 | weekSaleroom: Random.float(10000, 99999, 2, 2), 15 | amount: Random.float(99999, 999999, 2, 2), 16 | degree: Random.float(10, 99), 17 | }, 18 | //订单量 19 | orderLarge: { 20 | weekLarge: Random.float(10000, 99999, 2, 2), 21 | rise: Random.float(10, 99), 22 | decline: Random.float(10, 99), 23 | amount: Random.float(99999, 999999, 2, 2), 24 | }, 25 | //成交额度 26 | volume: { 27 | weekLarge: Random.float(10000, 99999, 2, 2), 28 | rise: Random.float(10, 99), 29 | decline: Random.float(10, 99), 30 | amount: Random.float(99999, 999999, 2, 2), 31 | }, 32 | }; 33 | 34 | export default [ 35 | //主控台 卡片数据 36 | { 37 | url: '/api/admin/dashboard/console', 38 | timeout: 1000, 39 | method: 'get', 40 | response: () => { 41 | return resultSuccess(consoleInfo); 42 | }, 43 | }, 44 | ]; 45 | -------------------------------------------------------------------------------- /src/components/Table/src/componentMap.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from 'vue'; 2 | import { 3 | NInput, 4 | NSelect, 5 | NCheckbox, 6 | NInputNumber, 7 | NSwitch, 8 | NDatePicker, 9 | NTimePicker, 10 | } from 'naive-ui'; 11 | import type { ComponentType } from './types/componentType'; 12 | 13 | export enum EventEnum { 14 | NInput = 'on-input', 15 | NInputNumber = 'on-input', 16 | NSelect = 'on-update:value', 17 | NSwitch = 'on-update:value', 18 | NCheckbox = 'on-update:value', 19 | NDatePicker = 'on-update:value', 20 | NTimePicker = 'on-update:value', 21 | } 22 | 23 | const componentMap = new Map(); 24 | 25 | componentMap.set('NInput', NInput); 26 | componentMap.set('NInputNumber', NInputNumber); 27 | componentMap.set('NSelect', NSelect); 28 | componentMap.set('NSwitch', NSwitch); 29 | componentMap.set('NCheckbox', NCheckbox); 30 | componentMap.set('NDatePicker', NDatePicker); 31 | componentMap.set('NTimePicker', NTimePicker); 32 | 33 | export function add(compName: ComponentType, component: Component) { 34 | componentMap.set(compName, component); 35 | } 36 | 37 | export function del(compName: ComponentType) { 38 | componentMap.delete(compName); 39 | } 40 | 41 | export { componentMap }; 42 | -------------------------------------------------------------------------------- /mock/user/menus.ts: -------------------------------------------------------------------------------- 1 | import { resultSuccess } from '../_util'; 2 | 3 | const menusList = [ 4 | { 5 | path: '/dashboard', 6 | name: 'Dashboard', 7 | component: 'LAYOUT', 8 | redirect: '/dashboard/console', 9 | meta: { 10 | icon: 'DashboardOutlined', 11 | title: 'Dashboard', 12 | }, 13 | children: [ 14 | { 15 | path: 'console', 16 | name: 'dashboard_console', 17 | component: '/dashboard/console/console', 18 | meta: { 19 | title: '主控台', 20 | }, 21 | }, 22 | { 23 | path: 'monitor', 24 | name: 'dashboard_monitor', 25 | component: '/dashboard/monitor/monitor', 26 | meta: { 27 | title: '监控页', 28 | }, 29 | }, 30 | { 31 | path: 'workplace', 32 | name: 'dashboard_workplace', 33 | component: '/dashboard/workplace/workplace', 34 | meta: { 35 | hidden: true, 36 | title: '工作台', 37 | }, 38 | }, 39 | ], 40 | }, 41 | ]; 42 | 43 | export default [ 44 | { 45 | url: '/api/admin/menus', 46 | timeout: 1000, 47 | method: 'get', 48 | response: () => { 49 | return resultSuccess(menusList); 50 | }, 51 | }, 52 | ]; 53 | -------------------------------------------------------------------------------- /src/utils/http/axios/checkStatus.ts: -------------------------------------------------------------------------------- 1 | export function checkStatus(status: number, msg: string): void { 2 | const $message = window['$message']; 3 | switch (status) { 4 | case 400: 5 | $message.error(msg); 6 | break 7 | // 401: 未登录 8 | // 未登录则跳转登录页面,并携带当前页面的路径 9 | // 在登录成功后返回当前页面,这一步需要在登录页操作。 10 | case 401: 11 | $message.error('用户没有权限(令牌、用户名、密码错误)!'); 12 | break 13 | case 403: 14 | $message.error('用户得到授权,但是访问是被禁止的。!'); 15 | break 16 | // 404请求不存在 17 | case 404: 18 | $message.error('网络请求错误,未找到该资源!'); 19 | break 20 | case 405: 21 | $message.error('网络请求错误,请求方法未允许!'); 22 | break 23 | case 408: 24 | $message.error('网络请求超时'); 25 | break 26 | case 500: 27 | $message.error('服务器错误,请联系管理员!'); 28 | break 29 | case 501: 30 | $message.error('网络未实现'); 31 | break 32 | case 502: 33 | $message.error('网络错误'); 34 | break 35 | case 503: 36 | $message.error('服务不可用,服务器暂时过载或维护!'); 37 | break 38 | case 504: 39 | $message.error('网络超时'); 40 | break 41 | case 505: 42 | $message.error('http版本不支持该请求!'); 43 | break 44 | default: 45 | $message.error(msg); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/Table/src/types/table.ts: -------------------------------------------------------------------------------- 1 | import type { InternalRowData, TableBaseColumn } from 'naive-ui/lib/data-table/src/interface'; 2 | import { ComponentType } from './componentType'; 3 | export interface BasicColumn extends TableBaseColumn { 4 | //编辑表格 5 | edit?: boolean; 6 | editRow?: boolean; 7 | editable?: boolean; 8 | editComponent?: ComponentType; 9 | editComponentProps?: Recordable; 10 | editRule?: boolean | ((text: string, record: Recordable) => Promise); 11 | editValueMap?: (value: any) => string; 12 | onEditRow?: () => void; 13 | // 权限编码控制是否显示 14 | auth?: string[]; 15 | // 业务控制是否显示 16 | ifShow?: boolean | ((column: BasicColumn) => boolean); 17 | // 控制是否支持拖拽,默认支持 18 | draggable?: boolean; 19 | } 20 | 21 | export interface TableActionType { 22 | reload: (opt) => Promise; 23 | emit?: any; 24 | getColumns: (opt?) => BasicColumn[]; 25 | setColumns: (columns: BasicColumn[] | string[]) => void; 26 | } 27 | 28 | export interface BasicTableProps { 29 | title?: string; 30 | dataSource: Function; 31 | columns: any[]; 32 | pagination: object; 33 | showPagination: boolean; 34 | actionColumn: any[]; 35 | canResize: boolean; 36 | resizeHeightOffset: number; 37 | loading: boolean; 38 | } 39 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './styles/tailwind.css'; 2 | import './styles/index.less'; 3 | import { createApp } from 'vue'; 4 | import { setupNaiveDiscreteApi, setupNaive, setupDirectives } from '@/plugins'; 5 | import App from './App.vue'; 6 | import router, { setupRouter } from './router'; 7 | import { setupStore } from '@/store'; 8 | 9 | async function bootstrap() { 10 | const app = createApp(App); 11 | 12 | // 挂载状态管理 13 | setupStore(app); 14 | 15 | // 注册全局常用的 naive-ui 组件 16 | setupNaive(app); 17 | 18 | // 挂载 naive-ui 脱离上下文的 Api 19 | setupNaiveDiscreteApi(); 20 | 21 | // 注册全局自定义组件 22 | //setupCustomComponents(); 23 | 24 | // 注册全局自定义指令,如:v-permission权限指令 25 | setupDirectives(app); 26 | 27 | // 注册全局方法,如:app.config.globalProperties.$message = message 28 | //setupGlobalMethods(app); 29 | 30 | // 挂载路由 31 | setupRouter(app); 32 | 33 | // 路由准备就绪后挂载 APP 实例 34 | // https://router.vuejs.org/api/interfaces/router.html#isready 35 | await router.isReady(); 36 | 37 | // https://www.naiveui.com/en-US/os-theme/docs/style-conflict#About-Tailwind's-Preflight-Style-Override 38 | const meta = document.createElement('meta'); 39 | meta.name = 'naive-ui-style'; 40 | document.head.appendChild(meta); 41 | 42 | app.mount('#app', true); 43 | } 44 | 45 | void bootstrap(); 46 | -------------------------------------------------------------------------------- /types/mcp.d.ts: -------------------------------------------------------------------------------- 1 | export interface PresetParam { 2 | name: string 3 | title: string 4 | value: any 5 | require_encrypt: boolean 6 | encrypted: boolean 7 | } 8 | export interface McpCustomizedParamDefinition { 9 | name: string 10 | title: string 11 | require_encrypt: boolean 12 | } 13 | export interface McpInfo { 14 | id: string 15 | uuid: string 16 | title: string 17 | transportType: string 18 | sseUrl: string 19 | sseTimeout: number 20 | stdioCommand: string 21 | stdioArg: string 22 | presetParams: PresetParam[] 23 | customizedParamDefinitions: McpCustomizedParamDefinition[] 24 | installType: string 25 | website: string 26 | remark: string 27 | isEnable: boolean 28 | createTime: string 29 | updateTime: string 30 | } 31 | 32 | export interface McpSearchReq { 33 | title: string 34 | transportType: string 35 | installType: string 36 | isEnable: boolean 37 | } 38 | 39 | export interface McpAddOrEditReq { 40 | uuid: string 41 | title: string 42 | transportType: string 43 | sseUrl: string 44 | sseTimeout: number 45 | stdioCommand: string 46 | stdioArg: string 47 | presetParams: PresetParam[] 48 | customizedParamDefinitions: McpCustomizedParamDefinition[] 49 | installType: string 50 | repoUrl: string 51 | remark: string 52 | isEnable: boolean 53 | } -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 45 | -------------------------------------------------------------------------------- /src/router/modules/workflow.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { Flow } from '@vicons/carbon'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | { 19 | path: '/workflow', 20 | name: 'Workflow', 21 | redirect: '/workflow/component', 22 | component: Layout, 23 | meta: { 24 | title: '流程编排', 25 | icon: renderIcon(Flow), 26 | sort: 5, 27 | }, 28 | children: [ 29 | { 30 | path: 'component', 31 | name: 'Components', 32 | meta: { 33 | title: '基础组件', 34 | }, 35 | component: () => import('@/views/workflow/wf-component/index.vue'), 36 | }, 37 | { 38 | path: 'list', 39 | name: 'Workflows', 40 | meta: { 41 | title: '流程列表', 42 | }, 43 | component: () => import('@/views/workflow/index.vue'), 44 | } 45 | ], 46 | }, 47 | ]; 48 | 49 | export default routes; 50 | -------------------------------------------------------------------------------- /src/api/aiModel.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios' 2 | 3 | 4 | function search(data, params: { current: number, size: number }) { 5 | return http.request({ 6 | url: `/admin/model/search?currentPage=${params.current}&pageSize=${params.size}`, 7 | method: 'post', 8 | data 9 | }) 10 | } 11 | 12 | function disable(id: string) { 13 | return http.request({ 14 | url: '/admin/model/edit', 15 | method: 'post', 16 | data: { 17 | id, 18 | isEnable: false 19 | } 20 | }) 21 | } 22 | 23 | function enable(id: string) { 24 | return http.request({ 25 | url: '/admin/model/edit', 26 | method: 'post', 27 | data: { 28 | id, 29 | isEnable: true 30 | } 31 | }) 32 | } 33 | 34 | function addOne(data) { 35 | return http.request({ 36 | url: '/admin/model/addOne', 37 | method: 'post', 38 | data 39 | }) 40 | } 41 | 42 | function edit(data) { 43 | return http.request({ 44 | url: '/admin/model/edit', 45 | method: 'post', 46 | data 47 | }) 48 | } 49 | 50 | function deleteOne(id: string) { 51 | return http.request( 52 | { 53 | url: `/admin/model/del/${id}`, 54 | method: 'POST', 55 | } 56 | ) 57 | } 58 | 59 | 60 | export default { 61 | addOne, 62 | edit, 63 | search, 64 | disable, 65 | enable, 66 | deleteOne, 67 | } 68 | -------------------------------------------------------------------------------- /src/assets/images/nav-horizontal-mix.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | -------------------------------------------------------------------------------- /src/hooks/setting/useProjectSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useProjectSettingStore } from '@/store/modules/projectSetting'; 3 | 4 | export function useProjectSetting() { 5 | const projectStore = useProjectSettingStore(); 6 | 7 | const navMode = computed(() => projectStore.navMode); 8 | 9 | const navTheme = computed(() => projectStore.navTheme); 10 | 11 | const isMobile = computed(() => projectStore.isMobile); 12 | 13 | const headerSetting = computed(() => projectStore.headerSetting); 14 | 15 | const multiTabsSetting = computed(() => projectStore.multiTabsSetting); 16 | 17 | const menuSetting = computed(() => projectStore.menuSetting); 18 | 19 | const crumbsSetting = computed(() => projectStore.crumbsSetting); 20 | 21 | const permissionMode = computed(() => projectStore.permissionMode); 22 | 23 | const showFooter = computed(() => projectStore.showFooter); 24 | 25 | const isPageAnimate = computed(() => projectStore.isPageAnimate); 26 | 27 | const pageAnimateType = computed(() => projectStore.pageAnimateType); 28 | 29 | return { 30 | navMode, 31 | navTheme, 32 | isMobile, 33 | headerSetting, 34 | multiTabsSetting, 35 | menuSetting, 36 | crumbsSetting, 37 | permissionMode, 38 | showFooter, 39 | isPageAnimate, 40 | pageAnimateType, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strictFunctionTypes": false, 10 | "jsx": "preserve", 11 | "baseUrl": ".", 12 | "allowJs": true, 13 | "sourceMap": true, 14 | "esModuleInterop": true, 15 | "resolveJsonModule": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "experimentalDecorators": true, 19 | "lib": [ 20 | "dom", 21 | "esnext" 22 | ], 23 | "typeRoots": [ 24 | "./node_modules/@types/", 25 | "./types" 26 | ], 27 | "noImplicitAny": false, 28 | "skipLibCheck": true, 29 | "paths": { 30 | "@/*": [ 31 | "src/*" 32 | ], 33 | "/#/*": [ 34 | "types/*" 35 | ] 36 | } 37 | }, 38 | "include": [ 39 | "src/**/*.ts", 40 | "src/**/*.d.ts", 41 | "src/**/*.tsx", 42 | "src/**/*.vue", 43 | "types/**/*.d.ts", 44 | "types/**/*.ts", 45 | "build/**/*.ts", 46 | "build/**/*.d.ts", 47 | "mock/**/*.ts", 48 | "components.d.ts", 49 | "vite.config.ts" 50 | ], 51 | "exclude": [ 52 | "node_modules", 53 | "dist", 54 | "**/*.js" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /mock/system/role.ts: -------------------------------------------------------------------------------- 1 | import { resultSuccess, doCustomTimes } from '../_util'; 2 | 3 | function getMenuKeys() { 4 | const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail']; 5 | const newKeys = []; 6 | doCustomTimes(parseInt(Math.random() * 6), () => { 7 | const key = keys[Math.floor(Math.random() * keys.length)]; 8 | newKeys.push(key); 9 | }) 10 | return Array.from(new Set(newKeys)); 11 | } 12 | 13 | const roleList = (pageSize) => { 14 | const result: any[] = []; 15 | doCustomTimes(pageSize, () => { 16 | result.push({ 17 | id: '@integer(10,100)', 18 | name: '@cname()', 19 | explain: '@cname()', 20 | isDefault: '@boolean()', 21 | menu_keys: getMenuKeys(), 22 | create_date: "@date('yyyy-MM-dd hh:mm:ss')", 23 | 'status|1': ['normal', 'enable', 'disable'], 24 | }); 25 | }) 26 | return result; 27 | } 28 | 29 | export default [ 30 | { 31 | url: '/api/admin/role/list', 32 | timeout: 1000, 33 | method: 'get', 34 | response: ({ query }) => { 35 | const { page = 1, pageSize = 10 } = query; 36 | const list = roleList(Number(pageSize)); 37 | return resultSuccess({ 38 | page: Number(page), 39 | pageSize: Number(pageSize), 40 | pageCount: 60, 41 | list, 42 | }); 43 | }, 44 | }, 45 | ]; 46 | -------------------------------------------------------------------------------- /src/utils/http/axios/helper.ts: -------------------------------------------------------------------------------- 1 | import { isObject, isString } from '@/utils/is'; 2 | 3 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; 4 | 5 | export function joinTimestamp( 6 | join: boolean, 7 | restful: T 8 | ): T extends true ? string : object; 9 | 10 | export function joinTimestamp(join: boolean, restful = false): string | object { 11 | if (!join) { 12 | return restful ? '' : {}; 13 | } 14 | const now = new Date().getTime(); 15 | if (restful) { 16 | return `?_t=${now}`; 17 | } 18 | return { _t: now }; 19 | } 20 | 21 | /** 22 | * @description: Format request parameter time 23 | */ 24 | export function formatRequestDate(params: Recordable) { 25 | if (Object.prototype.toString.call(params) !== '[object Object]') { 26 | return; 27 | } 28 | 29 | for (const key in params) { 30 | if (params[key] && params[key]._isAMomentObject) { 31 | params[key] = params[key].format(DATE_TIME_FORMAT); 32 | } 33 | if (isString(key)) { 34 | const value = params[key]; 35 | if (value) { 36 | try { 37 | params[key] = isString(value) ? value.trim() : value; 38 | } catch (error) { 39 | throw new Error(error as any); 40 | } 41 | } 42 | } 43 | if (isObject(params[key])) { 44 | formatRequestDate(params[key]); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mock/user/user.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs'; 2 | import { resultSuccess } from '../_util'; 3 | 4 | const Random = Mock.Random; 5 | 6 | const token = Random.string('upper', 32, 32); 7 | 8 | const adminInfo = { 9 | userId: '1', 10 | username: 'admin', 11 | realName: 'Admin', 12 | avatar: Random.image(), 13 | desc: 'manager', 14 | password: Random.string('upper', 4, 16), 15 | token, 16 | permissions: [ 17 | { 18 | label: '主控台', 19 | value: 'dashboard_console', 20 | }, 21 | { 22 | label: '监控页', 23 | value: 'dashboard_monitor', 24 | }, 25 | { 26 | label: '工作台', 27 | value: 'dashboard_workplace', 28 | }, 29 | { 30 | label: '基础列表', 31 | value: 'basic_list', 32 | }, 33 | { 34 | label: '基础列表删除', 35 | value: 'basic_list_delete', 36 | }, 37 | ], 38 | }; 39 | 40 | export default [ 41 | { 42 | url: '/api/admin/login', 43 | timeout: 1000, 44 | method: 'post', 45 | response: () => { 46 | return resultSuccess({ token }); 47 | }, 48 | }, 49 | { 50 | url: '/api/admin/admin_info', 51 | timeout: 1000, 52 | method: 'get', 53 | response: () => { 54 | // const token = getRequestToken(request); 55 | // if (!token) return resultError('Invalid token'); 56 | return resultSuccess(adminInfo); 57 | }, 58 | }, 59 | ]; 60 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/index.ts: -------------------------------------------------------------------------------- 1 | import type { BasicColumn } from '@/components/Table/src/types/table'; 2 | import { h, Ref } from 'vue'; 3 | 4 | import EditableCell from './EditableCell.vue'; 5 | 6 | export function renderEditCell(column: BasicColumn) { 7 | return (record, index) => { 8 | const _key = column.key; 9 | const value = record[_key]; 10 | record.onEdit = async (edit: boolean, submit = false) => { 11 | if (!submit) { 12 | record.editable = edit; 13 | } 14 | 15 | if (!edit && submit) { 16 | const res = await record.onSubmitEdit?.(); 17 | if (res) { 18 | record.editable = false; 19 | return true; 20 | } 21 | return false; 22 | } 23 | // cancel 24 | if (!edit && !submit) { 25 | record.onCancelEdit?.(); 26 | } 27 | return true; 28 | } 29 | return h(EditableCell, { 30 | value, 31 | record, 32 | column, 33 | index, 34 | }); 35 | } 36 | } 37 | 38 | export type EditRecordRow = Partial< 39 | { 40 | onEdit: (editable: boolean, submit?: boolean) => Promise; 41 | editable: boolean; 42 | onCancel: Fn; 43 | onSubmit: Fn; 44 | submitCbs: Fn[]; 45 | cancelCbs: Fn[]; 46 | validCbs: Fn[]; 47 | editValueRefs: Recordable; 48 | } & T 49 | >; 50 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/CellComponent.ts: -------------------------------------------------------------------------------- 1 | import type { FunctionalComponent, defineComponent } from 'vue'; 2 | import type { ComponentType } from '../../types/componentType'; 3 | import { componentMap } from '@/components/Table/src/componentMap'; 4 | 5 | import { h } from 'vue'; 6 | 7 | import { NPopover } from 'naive-ui'; 8 | 9 | export interface ComponentProps { 10 | component: ComponentType; 11 | rule: boolean; 12 | popoverVisible: boolean; 13 | ruleMessage: string; 14 | } 15 | 16 | export const CellComponent: FunctionalComponent = ( 17 | { component = 'NInput', rule = true, ruleMessage, popoverVisible }: ComponentProps, 18 | { attrs } 19 | ) => { 20 | const Comp = componentMap.get(component) as typeof defineComponent; 21 | 22 | const DefaultComp = h(Comp, attrs); 23 | if (!rule) { 24 | return DefaultComp; 25 | } 26 | return h( 27 | NPopover, 28 | { 'display-directive': 'show', show: !!popoverVisible, manual: 'manual' }, 29 | { 30 | trigger: () => DefaultComp, 31 | default: () => 32 | h( 33 | 'span', 34 | { 35 | style: { 36 | color: 'red', 37 | width: '90px', 38 | display: 'inline-block', 39 | }, 40 | }, 41 | { 42 | default: () => ruleMessage, 43 | } 44 | ), 45 | } 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/plugins/naiveDiscreteApi.ts: -------------------------------------------------------------------------------- 1 | import * as NaiveUI from 'naive-ui'; 2 | import { computed } from 'vue'; 3 | import { useDesignSetting } from '@/store/modules/designSetting'; 4 | import { lighten } from '@/utils/index'; 5 | 6 | /** 7 | * 挂载 Naive-ui 脱离上下文的 API 8 | * 如果你想在 setup 外使用 useDialog、useMessage、useNotification、useLoadingBar,可以通过 createDiscreteApi 来构建对应的 API。 9 | * https://www.naiveui.com/zh-CN/dark/components/discrete 10 | */ 11 | 12 | export function setupNaiveDiscreteApi() { 13 | const designStore = useDesignSetting(); 14 | 15 | const configProviderPropsRef = computed(() => ({ 16 | theme: designStore.darkTheme ? NaiveUI.darkTheme : undefined, 17 | themeOverrides: { 18 | common: { 19 | primaryColor: designStore.appTheme, 20 | primaryColorHover: lighten(designStore.appTheme, 6), 21 | primaryColorPressed: lighten(designStore.appTheme, 6), 22 | }, 23 | LoadingBar: { 24 | colorLoading: designStore.appTheme, 25 | }, 26 | }, 27 | })); 28 | const { message, dialog, notification, loadingBar } = NaiveUI.createDiscreteApi( 29 | ['message', 'dialog', 'notification', 'loadingBar'], 30 | { 31 | configProviderProps: configProviderPropsRef, 32 | } 33 | ); 34 | 35 | window['$message'] = message; 36 | window['$dialog'] = dialog; 37 | window['$notification'] = notification; 38 | window['$loading'] = loadingBar; 39 | } 40 | -------------------------------------------------------------------------------- /src/layout/components/Footer/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 23 | 24 | 57 | -------------------------------------------------------------------------------- /src/directives/longpress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-longpress 3 | * 长按指令,长按时触发事件 4 | */ 5 | import type { Directive, DirectiveBinding } from 'vue'; 6 | 7 | const directive: Directive = { 8 | mounted(el: HTMLElement, binding: DirectiveBinding) { 9 | if (typeof binding.value !== 'function') { 10 | throw 'callback must be a function'; 11 | } 12 | // 定义变量 13 | let pressTimer: any = null; 14 | // 创建计时器( 2秒后执行函数 ) 15 | const start = (e: any) => { 16 | if (e.button) { 17 | if (e.type === 'click' && e.button !== 0) { 18 | return; 19 | } 20 | } 21 | if (pressTimer === null) { 22 | pressTimer = setTimeout(() => { 23 | handler(e); 24 | }, 1000); 25 | } 26 | }; 27 | // 取消计时器 28 | const cancel = () => { 29 | if (pressTimer !== null) { 30 | clearTimeout(pressTimer); 31 | pressTimer = null; 32 | } 33 | }; 34 | // 运行函数 35 | const handler = (e: MouseEvent | TouchEvent) => { 36 | binding.value(e); 37 | } 38 | // 添加事件监听器 39 | el.addEventListener('mousedown', start); 40 | el.addEventListener('touchstart', start); 41 | // 取消计时器 42 | el.addEventListener('click', cancel); 43 | el.addEventListener('mouseout', cancel); 44 | el.addEventListener('touchend', cancel); 45 | el.addEventListener('touchcancel', cancel); 46 | }, 47 | }; 48 | 49 | export default directive; 50 | -------------------------------------------------------------------------------- /src/router/modules/aiModel.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { CodeSandboxOutlined } from '@vicons/antd'; 4 | import { renderIconWithProps } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | { 19 | path: '/ai-model', 20 | name: 'AiModel', 21 | redirect: '/ai-model/list', 22 | component: Layout, 23 | meta: { 24 | title: '模型管理', 25 | icon: renderIconWithProps(CodeSandboxOutlined, {size: 24}), 26 | sort: 3, 27 | }, 28 | children: [ 29 | { 30 | path: 'platform', 31 | name: 'AiModelPlatform', 32 | meta: { 33 | title: '平台配置', 34 | activeMenu: 'AiModelPlatform', 35 | }, 36 | component: () => import('@/views/ai-model/platform.vue'), 37 | }, 38 | { 39 | path: 'list', 40 | name: 'AiModelList', 41 | meta: { 42 | title: '模型管理', 43 | }, 44 | component: () => import('@/views/ai-model/index.vue'), 45 | }, 46 | ], 47 | }, 48 | ]; 49 | 50 | export default routes; 51 | -------------------------------------------------------------------------------- /src/router/modules/conversation.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router' 2 | import { Layout } from '@/router/constant' 3 | import { ChatboxEllipsesOutline } from '@vicons/ionicons5' 4 | import { renderIconWithProps } from '@/utils/index' 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | { 19 | path: '/conversation', 20 | name: 'Conversation', 21 | redirect: '/conversation/list', 22 | component: Layout, 23 | meta: { 24 | title: '会话管理', 25 | icon: renderIconWithProps(ChatboxEllipsesOutline, { size: 20 }), 26 | sort: 3, 27 | }, 28 | children: [ 29 | { 30 | path: 'list', 31 | name: 'ConversationList', 32 | meta: { 33 | title: '会话管理', 34 | }, 35 | component: () => import('@/views/conversation/index.vue'), 36 | }, 37 | { 38 | path: 'preset', 39 | name: 'PresetConversationList', 40 | meta: { 41 | title: '预设会话管理', 42 | }, 43 | component: () => import('@/views/conversation/preset-conv/PresetConv.vue'), 44 | }, 45 | ], 46 | }, 47 | ] 48 | 49 | export default routes 50 | -------------------------------------------------------------------------------- /src/router/modules/knowledgeBase.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { LibraryOutline } from '@vicons/ionicons5'; 4 | import { renderIconWithProps } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | { 19 | path: '/knowledge-base', 20 | name: 'KnowledgeBase', 21 | redirect: '/knowledge-base/list', 22 | component: Layout, 23 | meta: { 24 | title: '知识库管理', 25 | icon: renderIconWithProps(LibraryOutline, {size: 20}), 26 | sort: 3, 27 | }, 28 | children: [ 29 | { 30 | path: 'list', 31 | name: 'KnowledgeBaseList', 32 | meta: { 33 | title: '知识库管理', 34 | }, 35 | component: () => import('@/views/knowledge-base/index.vue'), 36 | }, 37 | { 38 | path: 'info/:uuid?', 39 | name: 'KnowledgeBaseInfo', 40 | meta: { 41 | title: '知识库详情', 42 | hidden: true, 43 | activeMenu: 'KnowledgeBaseList', 44 | }, 45 | component: () => import('@/views/knowledge-base/index.vue'), 46 | }, 47 | ], 48 | }, 49 | ]; 50 | 51 | export default routes; 52 | -------------------------------------------------------------------------------- /src/router/types.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw, RouteMeta } from 'vue-router'; 2 | import { defineComponent } from 'vue'; 3 | 4 | export type Component = 5 | | ReturnType 6 | | (() => Promise) 7 | | (() => Promise); 8 | 9 | export interface AppRouteRecordRaw extends Omit { 10 | name: string; 11 | meta: RouteMeta; 12 | component?: Component | string; 13 | components?: Component; 14 | children?: AppRouteRecordRaw[]; 15 | props?: Recordable; 16 | fullPath?: string; 17 | } 18 | 19 | export interface Meta { 20 | // 名称 21 | title: string; 22 | // 是否忽略权限 23 | ignoreAuth?: boolean; 24 | permissions?: string[]; 25 | // 是否不缓存 26 | noKeepAlive?: boolean; 27 | // 是否固定在tab上 28 | affix?: boolean; 29 | // tab上的图标 30 | icon?: string; 31 | // 跳转地址 32 | frameSrc?: string; 33 | // 外链跳转地址 34 | externalLink?: string; 35 | //隐藏 36 | hidden?: boolean; 37 | } 38 | 39 | export interface Menu { 40 | title: string; 41 | label: string; 42 | key: string; 43 | meta: RouteMeta; 44 | name: string; 45 | component?: Component | string; 46 | components?: Component; 47 | children?: AppRouteRecordRaw[]; 48 | props?: Recordable; 49 | fullPath?: string; 50 | icon?: any; 51 | path: string; 52 | permissions?: string[]; 53 | redirect?: string; 54 | sort?: number; 55 | } 56 | 57 | export interface IModuleType { 58 | default: Array | RouteRecordRaw; 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/http/axios/axiosTransform.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 数据处理类,可以根据项目自行配置 3 | */ 4 | import type { InternalAxiosRequestConfig, AxiosRequestConfig, AxiosResponse } from 'axios'; 5 | import type { RequestOptions, Result } from './types'; 6 | 7 | export interface CreateAxiosOptions extends AxiosRequestConfig { 8 | authenticationScheme?: string; 9 | transform?: AxiosTransform; 10 | requestOptions?: RequestOptions; 11 | } 12 | 13 | export abstract class AxiosTransform { 14 | /** 15 | * @description: 请求之前处理配置 16 | * @description: Process configuration before request 17 | */ 18 | beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig; 19 | 20 | /** 21 | * @description: 请求成功处理 22 | */ 23 | transformRequestData?: (res: AxiosResponse, options: RequestOptions) => any; 24 | 25 | /** 26 | * @description: 请求失败处理 27 | */ 28 | requestCatch?: (e: Error) => Promise; 29 | 30 | /** 31 | * @description: 请求之前的拦截器 32 | */ 33 | requestInterceptors?: ( 34 | config: InternalAxiosRequestConfig, 35 | options: CreateAxiosOptions 36 | ) => InternalAxiosRequestConfig; 37 | 38 | /** 39 | * @description: 请求之后的拦截器 40 | */ 41 | responseInterceptors?: (res: AxiosResponse) => AxiosResponse; 42 | 43 | /** 44 | * @description: 请求之前的拦截器错误处理 45 | */ 46 | requestInterceptorsCatch?: (error: Error) => void; 47 | 48 | /** 49 | * @description: 请求之后的拦截器错误处理 50 | */ 51 | responseInterceptorsCatch?: (error: Error) => void; 52 | } 53 | -------------------------------------------------------------------------------- /src/directives/draggable.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。 3 | 4 | 思路: 5 | 1、设置需要拖拽的元素为absolute,其父元素为relative。 6 | 2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。 7 | 3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值 8 | 4、鼠标松开(onmouseup)时完成一次拖拽 9 | 10 | 使用:在 Dom 上加上 v-draggable 即可 11 | 12 | */ 13 | import type { Directive } from 'vue'; 14 | interface ElType extends HTMLElement { 15 | parentNode: any; 16 | } 17 | const draggable: Directive = { 18 | mounted: function (el: ElType) { 19 | el.style.cursor = 'move'; 20 | el.style.position = 'absolute'; 21 | el.onmousedown = function (e) { 22 | const disX = e.pageX - el.offsetLeft; 23 | const disY = e.pageY - el.offsetTop; 24 | document.onmousemove = function (e) { 25 | let x = e.pageX - disX; 26 | let y = e.pageY - disY; 27 | const maxX = el.parentNode.offsetWidth - el.offsetWidth; 28 | const maxY = el.parentNode.offsetHeight - el.offsetHeight; 29 | if (x < 0) { 30 | x = 0; 31 | } else if (x > maxX) { 32 | x = maxX; 33 | } 34 | 35 | if (y < 0) { 36 | y = 0; 37 | } else if (y > maxY) { 38 | y = maxY; 39 | } 40 | el.style.left = x + 'px'; 41 | el.style.top = y + 'px'; 42 | } 43 | document.onmouseup = function () { 44 | document.onmousemove = document.onmouseup = null; 45 | } 46 | } 47 | }, 48 | }; 49 | export default draggable; 50 | -------------------------------------------------------------------------------- /src/views/dashboard/console/components/VisitAmount.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 60 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignores: [(commit) => commit.includes('init')], 3 | extends: ['@commitlint/config-conventional'], 4 | parserPreset: { 5 | parserOpts: { 6 | headerPattern: /^(\w*|[\u4e00-\u9fa5]*)(?:[\(\(](.*)[\)\)])?[\:\:] (.*)/, 7 | headerCorrespondence: ['type', 'scope', 'subject'], 8 | referenceActions: [ 9 | 'close', 10 | 'closes', 11 | 'closed', 12 | 'fix', 13 | 'fixes', 14 | 'fixed', 15 | 'resolve', 16 | 'resolves', 17 | 'resolved', 18 | ], 19 | issuePrefixes: ['#'], 20 | noteKeywords: ['BREAKING CHANGE'], 21 | fieldPattern: /^-(.*?)-$/, 22 | revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\./, 23 | revertCorrespondence: ['header', 'hash'], 24 | warn() {}, 25 | mergePattern: null, 26 | mergeCorrespondence: null, 27 | }, 28 | }, 29 | rules: { 30 | 'body-leading-blank': [2, 'always'], 31 | 'footer-leading-blank': [1, 'always'], 32 | 'header-max-length': [2, 'always', 108], 33 | 'subject-empty': [2, 'never'], 34 | 'type-empty': [2, 'never'], 35 | 'type-enum': [ 36 | 2, 37 | 'always', 38 | [ 39 | 'feat', 40 | 'fix', 41 | 'perf', 42 | 'style', 43 | 'docs', 44 | 'test', 45 | 'refactor', 46 | 'build', 47 | 'ci', 48 | 'chore', 49 | 'revert', 50 | 'wip', 51 | 'workflow', 52 | 'types', 53 | 'release', 54 | ], 55 | ], 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /src/utils/http/axios/types.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig } from 'axios'; 2 | import { AxiosTransform } from './axiosTransform'; 3 | 4 | export interface CreateAxiosOptions extends AxiosRequestConfig { 5 | transform?: AxiosTransform; 6 | requestOptions?: RequestOptions; 7 | authenticationScheme?: string; 8 | } 9 | 10 | // 上传文件 11 | export interface UploadFileParams { 12 | // 其他参数 13 | data?: Recordable; 14 | // 文件参数接口字段名 15 | name?: string; 16 | // 文件 17 | file: File | Blob; 18 | // 文件名称 19 | filename?: string; 20 | [key: string]: any; 21 | } 22 | 23 | export interface RequestOptions { 24 | // 请求参数拼接到url 25 | joinParamsToUrl?: boolean; 26 | // 格式化请求参数时间 27 | formatDate?: boolean; 28 | // 是否显示提示信息 29 | isShowMessage?: boolean; 30 | // 是否解析成JSON 31 | isParseToJson?: boolean; 32 | // 成功的文本信息 33 | successMessageText?: string; 34 | // 是否显示成功信息 35 | isShowSuccessMessage?: boolean; 36 | // 是否显示失败信息 37 | isShowErrorMessage?: boolean; 38 | // 错误的文本信息 39 | errorMessageText?: string; 40 | // 是否加入url 41 | joinPrefix?: boolean; 42 | // 接口地址, 不填则使用默认apiUrl 43 | apiUrl?: string; 44 | // 请求拼接路径 45 | urlPrefix?: string; 46 | // 错误消息提示类型 47 | errorMessageMode?: 'none' | 'modal'; 48 | // 是否添加时间戳 49 | joinTime?: boolean; 50 | // 不进行任何处理,直接返回 51 | isTransformResponse?: boolean; 52 | // 是否返回原生响应头 53 | isReturnNativeResponse?: boolean; 54 | //忽略重复请求 55 | ignoreCancelToken?: boolean; 56 | // 是否携带token 57 | withToken?: boolean; 58 | } 59 | 60 | export interface Result { 61 | code: string; 62 | success: boolean; 63 | message: string; 64 | data: T; 65 | } 66 | -------------------------------------------------------------------------------- /src/components/Table/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue'; 2 | import { propTypes } from '@/utils/propTypes'; 3 | import { BasicColumn } from './types/table'; 4 | import { NDataTable } from 'naive-ui'; 5 | export const basicProps = { 6 | ...NDataTable.props, // 这里继承原 UI 组件的 props 7 | title: { 8 | type: String, 9 | default: null, 10 | }, 11 | titleTooltip: { 12 | type: String, 13 | default: null, 14 | }, 15 | size: { 16 | type: String, 17 | default: 'medium', 18 | }, 19 | dataSource: { 20 | type: [Object], 21 | default: () => [], 22 | }, 23 | columns: { 24 | type: [Array] as PropType, 25 | default: () => [], 26 | required: true, 27 | }, 28 | beforeRequest: { 29 | type: Function as PropType<(...arg: any[]) => void | Promise>, 30 | default: null, 31 | }, 32 | request: { 33 | type: Function as PropType<(...arg: any[]) => Promise>, 34 | default: null, 35 | }, 36 | afterRequest: { 37 | type: Function as PropType<(...arg: any[]) => void | Promise>, 38 | default: null, 39 | }, 40 | rowKey: { 41 | type: [String, Function] as PropType string)>, 42 | default: undefined, 43 | }, 44 | pagination: { 45 | type: [Object, Boolean], 46 | default: () => {}, 47 | }, 48 | //废弃 49 | showPagination: { 50 | type: [String, Boolean], 51 | default: 'auto', 52 | }, 53 | actionColumn: { 54 | type: Object as PropType, 55 | default: null, 56 | }, 57 | canResize: propTypes.bool.def(true), 58 | resizeHeightOffset: propTypes.number.def(0), 59 | }; 60 | -------------------------------------------------------------------------------- /src/hooks/web/usePermission.ts: -------------------------------------------------------------------------------- 1 | import { useUserStore } from '@/store/modules/user'; 2 | 3 | export function usePermission() { 4 | const userStore = useUserStore(); 5 | 6 | /** 7 | * 检查权限 8 | * @param accesses 9 | */ 10 | function _somePermissions(accesses: string[]) { 11 | return userStore.getPermissions.some((item) => { 12 | const { value }: any = item; 13 | return accesses.includes(value); 14 | }) 15 | } 16 | 17 | /** 18 | * 判断是否存在权限 19 | * 可用于 v-if 显示逻辑 20 | * */ 21 | function hasPermission(accesses: string[]): boolean { 22 | if (!accesses || !accesses.length) return true; 23 | return _somePermissions(accesses); 24 | } 25 | 26 | /** 27 | * 是否包含指定的所有权限 28 | * @param accesses 29 | */ 30 | function hasEveryPermission(accesses: string[]): boolean { 31 | const permissionsList = userStore.getPermissions; 32 | if (Array.isArray(accesses)) { 33 | return permissionsList.every((access: any) => accesses.includes(access.value)); 34 | } 35 | throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`); 36 | } 37 | 38 | /** 39 | * 是否包含其中某个权限 40 | * @param accesses 41 | * @param accessMap 42 | */ 43 | function hasSomePermission(accesses: string[]): boolean { 44 | const permissionsList = userStore.getPermissions; 45 | if (Array.isArray(accesses)) { 46 | return permissionsList.some((access: any) => accesses.includes(access.value)); 47 | } 48 | throw new Error(`[hasSomePermission]: ${accesses} should be a array !`); 49 | } 50 | 51 | return { hasPermission, hasEveryPermission, hasSomePermission }; 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Modal/src/hooks/useModal.ts: -------------------------------------------------------------------------------- 1 | import { ref, unref, getCurrentInstance, watch } from 'vue'; 2 | import { isProdMode } from '@/utils/env'; 3 | import { ModalMethods, UseModalReturnType } from '../type'; 4 | import { getDynamicProps } from '@/utils'; 5 | import { tryOnUnmounted } from '@vueuse/core'; 6 | export function useModal(props): UseModalReturnType { 7 | const modalRef = ref>(null); 8 | const currentInstance = getCurrentInstance(); 9 | 10 | const getInstance = () => { 11 | const instance = unref(modalRef.value); 12 | if (!instance) { 13 | console.error('useModal instance is undefined!'); 14 | } 15 | return instance; 16 | } 17 | 18 | const register = (modalInstance: ModalMethods) => { 19 | isProdMode() && 20 | tryOnUnmounted(() => { 21 | modalRef.value = null; 22 | }) 23 | modalRef.value = modalInstance; 24 | currentInstance?.emit('register', modalInstance); 25 | 26 | watch( 27 | () => props, 28 | () => { 29 | props && modalInstance.setProps(getDynamicProps(props)); 30 | }, 31 | { 32 | immediate: true, 33 | deep: true, 34 | } 35 | ); 36 | } 37 | 38 | const methods: ModalMethods = { 39 | setProps: (props): void => { 40 | getInstance()?.setProps(props); 41 | }, 42 | openModal: () => { 43 | getInstance()?.openModal(); 44 | }, 45 | closeModal: () => { 46 | getInstance()?.closeModal(); 47 | }, 48 | setSubLoading: (status) => { 49 | getInstance()?.setSubLoading(status); 50 | }, 51 | }; 52 | 53 | return [register, methods]; 54 | } 55 | -------------------------------------------------------------------------------- /types/config.d.ts: -------------------------------------------------------------------------------- 1 | export interface ProjectSettingState { 2 | //导航模式 3 | navMode: string; 4 | //导航风格 5 | navTheme: string; 6 | //顶部设置 7 | headerSetting: object; 8 | //页脚 9 | showFooter: boolean; 10 | //菜单设置 11 | menuSetting: object; 12 | //多标签 13 | multiTabsSetting: object; 14 | //面包屑 15 | crumbsSetting: object; 16 | //权限模式 17 | permissionMode: string; 18 | } 19 | 20 | export interface IBodySetting { 21 | fixed: boolean; 22 | } 23 | 24 | export interface IHeaderSetting { 25 | bgColor: string; 26 | fixed: boolean; 27 | isReload: boolean; 28 | } 29 | 30 | export interface IMenuSetting { 31 | minMenuWidth: number; 32 | menuWidth: number; 33 | fixed: boolean; 34 | mixMenu: boolean; 35 | collapsed: boolean; 36 | mobileWidth: number; 37 | } 38 | 39 | export interface ICrumbsSetting { 40 | show: boolean; 41 | showIcon: boolean; 42 | } 43 | 44 | export interface IMultiTabsSetting { 45 | bgColor: string; 46 | fixed: boolean; 47 | show: boolean; 48 | } 49 | export interface GlobConfig { 50 | title: string; 51 | apiUrl: string; 52 | shortName: string; 53 | urlPrefix?: string; 54 | uploadUrl?: string; 55 | prodMock: boolean; 56 | imgUrl?: string; 57 | } 58 | 59 | export interface GlobEnvConfig { 60 | // 标题 61 | VITE_GLOB_APP_TITLE: string; 62 | // 接口地址 63 | VITE_GLOB_API_URL: string; 64 | // 接口前缀 65 | VITE_GLOB_API_URL_PREFIX?: string; 66 | // Project abbreviation 67 | VITE_GLOB_APP_SHORT_NAME: string; 68 | // 图片上传地址 69 | VITE_GLOB_UPLOAD_URL?: string; 70 | //图片前缀地址 71 | VITE_GLOB_IMG_URL?: string; 72 | //生产环境开启mock 73 | VITE_GLOB_PROD_MOCK: boolean; 74 | } 75 | -------------------------------------------------------------------------------- /src/api/conversation.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios' 2 | 3 | function searchPresetConvs(data, params: { current: number, size: number }) { 4 | return http.request({ 5 | url: `/admin/conv-preset/search?currentPage=${params.current}&pageSize=${params.size}`, 6 | method: 'post', 7 | data, 8 | }) 9 | } 10 | 11 | function addPresetConv(data) { 12 | return http.request({ 13 | url: '/admin/conv-preset/addOne', 14 | method: 'post', 15 | data, 16 | }) 17 | } 18 | 19 | function editPresetConv(uuid: string, data) { 20 | return http.request({ 21 | url: `/admin/conv-preset/edit/${uuid}`, 22 | method: 'post', 23 | data, 24 | }) 25 | } 26 | 27 | function deletePresetConv(uuid: string) { 28 | return http.request( 29 | { 30 | url: `/admin/conv-preset/del/${uuid}`, 31 | method: 'POST', 32 | } 33 | ) 34 | } 35 | 36 | function searchConvs(data, params: { current: number, size: number }) { 37 | return http.request({ 38 | url: `/admin/conv/search?currentPage=${params.current}&pageSize=${params.size}`, 39 | method: 'post', 40 | data, 41 | }) 42 | } 43 | 44 | function editConv(uuid: string, data) { 45 | return http.request({ 46 | url: `/admin/conv/edit/${uuid}`, 47 | method: 'post', 48 | data, 49 | }) 50 | } 51 | 52 | function deleteConv(uuid: string) { 53 | return http.request( 54 | { 55 | url: `/admin/conv/del/${uuid}`, 56 | method: 'POST', 57 | } 58 | ) 59 | } 60 | 61 | export default { 62 | searchPresetConvs, 63 | addPresetConv, 64 | editPresetConv, 65 | deletePresetConv, 66 | searchConvs, 67 | editConv, 68 | deleteConv, 69 | } -------------------------------------------------------------------------------- /src/utils/http/axios/axiosCancel.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig, Canceler } from 'axios'; 2 | import qs from 'qs'; 3 | 4 | import { isFunction } from '@/utils/is/index'; 5 | 6 | // 声明一个 Map 用于存储每个请求的标识 和 取消函数 7 | let pendingMap = new Map(); 8 | 9 | export const getPendingUrl = (config: AxiosRequestConfig) => 10 | [config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&'); 11 | 12 | export class AxiosCanceler { 13 | /** 14 | * 添加请求 15 | * @param {Object} config 16 | */ 17 | addPending(config: AxiosRequestConfig) { 18 | this.removePending(config); 19 | const url = getPendingUrl(config); 20 | config.cancelToken = 21 | config.cancelToken || 22 | new axios.CancelToken((cancel) => { 23 | if (!pendingMap.has(url)) { 24 | // 如果 pending 中不存在当前请求,则添加进去 25 | pendingMap.set(url, cancel); 26 | } 27 | }); 28 | } 29 | 30 | /** 31 | * @description: 清空所有pending 32 | */ 33 | removeAllPending() { 34 | pendingMap.forEach((cancel) => { 35 | cancel && isFunction(cancel) && cancel(); 36 | }) 37 | pendingMap.clear(); 38 | } 39 | 40 | /** 41 | * 移除请求 42 | * @param {Object} config 43 | */ 44 | removePending(config: AxiosRequestConfig) { 45 | const url = getPendingUrl(config); 46 | 47 | if (pendingMap.has(url)) { 48 | // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除 49 | const cancel = pendingMap.get(url); 50 | cancel && cancel(url); 51 | pendingMap.delete(url); 52 | } 53 | } 54 | 55 | /** 56 | * @description: 重置 57 | */ 58 | reset(): void { 59 | pendingMap = new Map(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Form/src/hooks/useFormValues.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '@/utils/is'; 2 | import { unref } from 'vue'; 3 | import type { Ref, ComputedRef } from 'vue'; 4 | import type { FormSchema } from '../types/form'; 5 | import { set } from 'lodash-es'; 6 | 7 | interface UseFormValuesContext { 8 | defaultFormModel: Ref; 9 | getSchema: ComputedRef; 10 | formModel: Recordable; 11 | } 12 | export function useFormValues({ defaultFormModel, getSchema, formModel }: UseFormValuesContext) { 13 | // 加工 form values 14 | function handleFormValues(values: Recordable) { 15 | if (!isObject(values)) { 16 | return {}; 17 | } 18 | const res: Recordable = {}; 19 | for (const item of Object.entries(values)) { 20 | let [, value] = item; 21 | const [key] = item; 22 | if ( 23 | !key || 24 | (isArray(value) && value.length === 0) || 25 | isFunction(value) || 26 | isNullOrUnDef(value) 27 | ) { 28 | continue; 29 | } 30 | // 删除空格 31 | if (isString(value)) { 32 | value = value.trim(); 33 | } 34 | set(res, key, value); 35 | } 36 | return res; 37 | } 38 | 39 | //初始化默认值 40 | function initDefault() { 41 | const schemas = unref(getSchema); 42 | const obj: Recordable = {}; 43 | schemas.forEach((item) => { 44 | const { defaultValue } = item; 45 | if (!isNullOrUnDef(defaultValue)) { 46 | obj[item.field] = defaultValue; 47 | formModel[item.field] = defaultValue; 48 | } 49 | }); 50 | defaultFormModel.value = obj; 51 | } 52 | 53 | return { handleFormValues, initDefault }; 54 | } 55 | -------------------------------------------------------------------------------- /src/hooks/useTime.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from 'vue'; 2 | 3 | /** 4 | * @description 获取本地时间 5 | */ 6 | export function useTime() { 7 | let timer; // 定时器 8 | const year = ref(0); // 年份 9 | const month = ref(0); // 月份 10 | const week = ref(''); // 星期几 11 | const day = ref(0); // 天数 12 | const hour = ref(0); // 小时 13 | const minute = ref(0); // 分钟 14 | const second = ref(0); // 秒 15 | 16 | // 更新时间 17 | const updateTime = () => { 18 | const date = new Date(); 19 | year.value = date.getFullYear(); 20 | month.value = date.getMonth() + 1; 21 | week.value = '日一二三四五六'.charAt(date.getDay()); 22 | day.value = date.getDate(); 23 | hour.value = 24 | (date.getHours() + '')?.padStart(2, '0') || 25 | new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours()); 26 | minute.value = 27 | (date.getMinutes() + '')?.padStart(2, '0') || 28 | new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes()); 29 | second.value = date.getSeconds(); 30 | } 31 | 32 | // 原生时间格式化 33 | // new Intl.DateTimeFormat('zh', { 34 | // year: 'numeric', 35 | // month: '2-digit', 36 | // day: '2-digit', 37 | // hour: '2-digit', 38 | // minute: '2-digit', 39 | // second: '2-digit', 40 | // hour12: false 41 | // }).format(new Date()) 42 | 43 | updateTime(); 44 | 45 | onMounted(() => { 46 | clearInterval(timer); 47 | timer = setInterval(() => updateTime(), 1000); 48 | }) 49 | 50 | onUnmounted(() => { 51 | clearInterval(timer); 52 | }) 53 | 54 | return { month, day, hour, minute, second, week }; 55 | } 56 | -------------------------------------------------------------------------------- /src/styles/transition/fade.less: -------------------------------------------------------------------------------- 1 | .fade-enter-active, 2 | .fade-leave-active { 3 | transition: opacity 0.2s ease-in-out; 4 | } 5 | 6 | .fade-enter-from, 7 | .fade-leave-to { 8 | opacity: 0; 9 | } 10 | 11 | /* fade-slide */ 12 | .fade-slide-leave-active, 13 | .fade-slide-enter-active { 14 | transition: all 0.3s; 15 | } 16 | 17 | .fade-slide-enter-from { 18 | opacity: 0; 19 | transform: translateX(-30px); 20 | } 21 | 22 | .fade-slide-leave-to { 23 | opacity: 0; 24 | transform: translateX(30px); 25 | } 26 | 27 | // /////////////////////////////////////////////// 28 | // Fade Bottom 29 | // /////////////////////////////////////////////// 30 | 31 | // Speed: 1x 32 | .fade-bottom-enter-active, 33 | .fade-bottom-leave-active { 34 | transition: opacity 0.25s, transform 0.3s; 35 | } 36 | 37 | .fade-bottom-enter-from { 38 | opacity: 0; 39 | transform: translateY(-10%); 40 | } 41 | 42 | .fade-bottom-leave-to { 43 | opacity: 0; 44 | transform: translateY(10%); 45 | } 46 | 47 | // fade-scale 48 | .fade-scale-leave-active, 49 | .fade-scale-enter-active { 50 | transition: all 0.28s; 51 | } 52 | 53 | .fade-scale-enter-from { 54 | opacity: 0; 55 | transform: scale(1.2); 56 | } 57 | 58 | .fade-scale-leave-to { 59 | opacity: 0; 60 | transform: scale(0.8); 61 | } 62 | 63 | // /////////////////////////////////////////////// 64 | // Fade Top 65 | // /////////////////////////////////////////////// 66 | 67 | // Speed: 1x 68 | .fade-top-enter-active, 69 | .fade-top-leave-active { 70 | transition: opacity 0.2s, transform 0.25s; 71 | } 72 | 73 | .fade-top-enter-from { 74 | opacity: 0; 75 | transform: translateY(8%); 76 | } 77 | 78 | .fade-top-leave-to { 79 | opacity: 0; 80 | transform: translateY(-8%); 81 | } 82 | -------------------------------------------------------------------------------- /src/views/user/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue' 2 | import { NAvatar } from 'naive-ui' 3 | import { BasicColumn } from '@/components/Table' 4 | export interface UserData { 5 | id: string 6 | uuid: string 7 | name: string 8 | email: string 9 | avatar: string 10 | userStatus: string 11 | activeTime: string 12 | createTime: string 13 | updateTime: string 14 | isAdmin: boolean 15 | } 16 | export const columns: BasicColumn[] = [ 17 | { 18 | title: 'id', 19 | key: 'id', 20 | width: 50, 21 | }, 22 | { 23 | title: 'uuid', 24 | key: 'uuid', 25 | width: 120, 26 | }, 27 | { 28 | title: '名称', 29 | key: 'name', 30 | width: 100, 31 | }, 32 | { 33 | title: '邮箱', 34 | key: 'email', 35 | width: 150, 36 | }, 37 | { 38 | title: '头像', 39 | key: 'avatar', 40 | width: 70, 41 | render(row) { 42 | return h(NAvatar, { 43 | size: 48, 44 | src: `/api/user/avatar/${row.uuid}`, 45 | }) 46 | }, 47 | }, 48 | { 49 | title: '状态', 50 | key: 'userStatus', 51 | width: 100, 52 | render(row) { 53 | if(row.userStatus === 'NORMAL'){ 54 | return '正常' 55 | }else if(row.userStatus === 'WAIT_CONFIRM'){ 56 | return '待激活' 57 | } 58 | return '已禁用' 59 | }, 60 | }, 61 | { 62 | title: '激活时间', 63 | key: 'activeTime', 64 | width: 180, 65 | }, 66 | { 67 | title: '创建时间', 68 | key: 'createTime', 69 | width: 180, 70 | }, 71 | { 72 | title: '更新时间', 73 | key: 'updateTime', 74 | width: 180, 75 | }, 76 | { 77 | title: '管理员', 78 | key: 'isAmdin', 79 | width: 100, 80 | render(row) { 81 | return row.isAdmin ? '是' : '否' 82 | }, 83 | }, 84 | ] 85 | -------------------------------------------------------------------------------- /mock/_util.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs'; 2 | 3 | export function resultSuccess(data, { message = 'ok' } = {}) { 4 | return Mock.mock({ 5 | code: '', 6 | data, 7 | message, 8 | success: true, 9 | }); 10 | } 11 | 12 | export function resultPageSuccess( 13 | page: number, 14 | pageSize: number, 15 | list: T[], 16 | { message = 'ok' } = {} 17 | ) { 18 | const pageData = pagination(page, pageSize, list); 19 | 20 | return { 21 | ...resultSuccess({ 22 | page, 23 | pageSize, 24 | pageCount: list.length, 25 | list: pageData, 26 | }), 27 | message, 28 | }; 29 | } 30 | 31 | export function resultError(message = 'Request failed', { code = -1, data = null } = {}) { 32 | return { 33 | code, 34 | data, 35 | message, 36 | success: false, 37 | }; 38 | } 39 | 40 | export function pagination(pageNo: number, pageSize: number, array: T[]): T[] { 41 | const offset = (pageNo - 1) * Number(pageSize); 42 | const ret = 43 | offset + Number(pageSize) >= array.length 44 | ? array.slice(offset, array.length) 45 | : array.slice(offset, offset + Number(pageSize)); 46 | return ret; 47 | } 48 | 49 | /** 50 | * @param {Number} times 回调函数需要执行的次数 51 | * @param {Function} callback 回调函数 52 | */ 53 | export function doCustomTimes(times: number, callback: any) { 54 | let i = -1; 55 | while (++i < times) { 56 | callback(i); 57 | } 58 | } 59 | 60 | export interface requestParams { 61 | method: string; 62 | body: any; 63 | headers?: { token?: string }; 64 | query: any; 65 | } 66 | 67 | /** 68 | * @description 本函数用于从request数据中获取token,请根据项目的实际情况修改 69 | * 70 | */ 71 | export function getRequestToken({ headers }: requestParams): string | undefined { 72 | return headers?.token; 73 | } 74 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; 3 | import { RedirectRoute } from '@/router/base'; 4 | import { PageEnum } from '@/enums/pageEnum'; 5 | import { createRouterGuards } from './guards'; 6 | import type { IModuleType } from './types'; 7 | 8 | const modules = import.meta.glob('./modules/**/*.ts', { eager: true }); 9 | 10 | const routeModuleList: RouteRecordRaw[] = Object.keys(modules).reduce((list, key) => { 11 | const mod = modules[key].default ?? {}; 12 | const modList = Array.isArray(mod) ? [...mod] : [mod]; 13 | return [...list, ...modList]; 14 | }, []); 15 | 16 | function sortRoute(a, b) { 17 | return (a.meta?.sort ?? 0) - (b.meta?.sort ?? 0); 18 | } 19 | 20 | routeModuleList.sort(sortRoute); 21 | 22 | export const RootRoute: RouteRecordRaw = { 23 | path: '/', 24 | name: 'Root', 25 | redirect: PageEnum.BASE_HOME, 26 | meta: { 27 | title: 'Root', 28 | }, 29 | }; 30 | 31 | export const LoginRoute: RouteRecordRaw = { 32 | path: '/login', 33 | name: 'Login', 34 | component: () => import('@/views/login/index.vue'), 35 | meta: { 36 | title: '登录', 37 | }, 38 | }; 39 | 40 | //需要验证权限 41 | export const asyncRoutes = [...routeModuleList]; 42 | 43 | //普通路由 无需验证权限 44 | export const constantRouter: RouteRecordRaw[] = [LoginRoute, RootRoute, RedirectRoute]; 45 | 46 | const router = createRouter({ 47 | history: createWebHashHistory(), //createWebHashHistory(),createWebHistory() 48 | routes: constantRouter, 49 | strict: true, 50 | scrollBehavior: () => ({ left: 0, top: 0 }), 51 | }); 52 | 53 | export function setupRouter(app: App) { 54 | app.use(router); 55 | // 创建路由守卫 56 | createRouterGuards(router); 57 | } 58 | 59 | export default router; 60 | -------------------------------------------------------------------------------- /src/views/ai-model/columns.ts: -------------------------------------------------------------------------------- 1 | import { BasicColumn } from '@/components/Table' 2 | import {MODEL_TYPES, DEFAULT_MODEL_PLATFORMS} from '@/utils/constants' 3 | import { AiModelData } from '/#/aiModel' 4 | 5 | export const allPlatforms = [...DEFAULT_MODEL_PLATFORMS] 6 | export const columns: BasicColumn[] = [ 7 | { 8 | title: 'id', 9 | key: 'id', 10 | width: 50, 11 | }, 12 | { 13 | title: '名称', 14 | key: 'name', 15 | width: 180, 16 | }, 17 | { 18 | title: '标题', 19 | key: 'title', 20 | width: 180, 21 | }, 22 | { 23 | title: '类型', 24 | key: 'type', 25 | width: 80, 26 | render(row) { 27 | return MODEL_TYPES.find(item => item.value === row.type)?.label 28 | }, 29 | }, 30 | { 31 | title: '平台', 32 | key: 'platform', 33 | width: 120, 34 | render(row){ 35 | return allPlatforms.find(item => item.value === row.platform)?.label 36 | } 37 | }, 38 | { 39 | title: '是否启用', 40 | key: 'isEnable', 41 | width: 80, 42 | render(row) { 43 | return row.isEnable ? '启用' : '禁用' 44 | }, 45 | }, 46 | { 47 | title: '是否免费', 48 | key: 'isFree', 49 | width: 80, 50 | render(row) { 51 | return row.isFree ? '是' : '否' 52 | }, 53 | }, 54 | { 55 | title: '上下文长度', 56 | key: 'contextWindow', 57 | width: 100, 58 | }, 59 | { 60 | title: '最大输入token数', 61 | key: 'maxInputTokens', 62 | width: 140, 63 | }, 64 | { 65 | title: '最大输出token数', 66 | key: 'maxOutputTokens', 67 | width: 140, 68 | }, 69 | { 70 | title: '配置', 71 | key: 'setting', 72 | }, 73 | { 74 | title: '创建时间', 75 | key: 'createTime', 76 | width: 150, 77 | }, 78 | { 79 | title: '更新时间', 80 | key: 'updateTime', 81 | width: 150, 82 | }, 83 | ] 84 | -------------------------------------------------------------------------------- /mock/table/list.ts: -------------------------------------------------------------------------------- 1 | import { Random } from 'mockjs'; 2 | import { resultSuccess, doCustomTimes } from '../_util'; 3 | 4 | const tableList = (pageSize) => { 5 | const result: any[] = []; 6 | doCustomTimes(pageSize, () => { 7 | result.push({ 8 | uuid: '@uuid()', 9 | id: '@integer(10,999999)', 10 | createTime: '@datetime', 11 | activeTime: '@datetime', 12 | email: '@email()', 13 | name: '@cname()', 14 | avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()), 15 | updateTime: '@datetime', 16 | 'no|100000-10000000': 100000, 17 | 'status|1': [true, false], 18 | }); 19 | }) 20 | return result; 21 | } 22 | 23 | export default [ 24 | //表格数据列表 25 | { 26 | url: '/api/admin/table/list', 27 | timeout: 1000, 28 | method: 'get', 29 | response: ({ query }) => { 30 | const { page = 1, pageSize = 10, name } = query; 31 | const list = tableList(Number(pageSize)); 32 | //并非真实,只是为了模拟搜索结果 33 | const count = name ? 30 : 60; 34 | return resultSuccess({ 35 | page: Number(page), 36 | pageSize: Number(pageSize), 37 | pageCount: count, 38 | itemCount: count * Number(pageSize), 39 | list, 40 | }); 41 | }, 42 | }, 43 | { 44 | url: '/api/admin/user/list', 45 | timeout: 1000, 46 | method: 'get', 47 | response: ({ query }) => { 48 | const { page = 1, pageSize = 10, name } = query; 49 | const list = tableList(Number(pageSize)); 50 | //并非真实,只是为了模拟搜索结果 51 | const count = name ? 30 : 60; 52 | return resultSuccess({ 53 | page: Number(page), 54 | pageSize: Number(pageSize), 55 | pageCount: count, 56 | itemCount: count * Number(pageSize), 57 | list, 58 | }); 59 | }, 60 | }, 61 | ]; 62 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_MODEL_PLATFORMS = [ 2 | { 3 | label: 'Openai', 4 | value: 'openai', 5 | }, 6 | { 7 | label: '灵积', 8 | value: 'dashscope', 9 | }, 10 | { 11 | label: '千帆', 12 | value: 'qianfan', 13 | }, 14 | { 15 | label: 'Ollama', 16 | value: 'ollama', 17 | }, 18 | { 19 | label: '深度求索', 20 | value: 'deepseek', 21 | }, 22 | { 23 | label: '硅基流动', 24 | value: 'siliconflow', 25 | }, 26 | ] 27 | export const MODEL_TYPES = [ 28 | { 29 | label: '文本', 30 | value: 'text', 31 | }, 32 | { 33 | label: '图像', 34 | value: 'image', 35 | }, 36 | { 37 | label: '嵌入', 38 | value: 'embedding', 39 | }, 40 | { 41 | label: '重排', 42 | value: 'rerank', 43 | }, 44 | { 45 | label: '文本转语音', 46 | value: 'tts', 47 | }, 48 | { 49 | label: '语音识别', 50 | value: 'asr', 51 | }, 52 | ] 53 | export const mcpTransportType = [ 54 | { 55 | label: '网络传输(sse)', 56 | value: 'sse', 57 | }, 58 | { 59 | label: '标准输入输出', 60 | value: 'stdio', 61 | }, 62 | ] 63 | export const mcpInstallType = [ 64 | { 65 | label: 'docker', 66 | value: 'docker', 67 | }, 68 | { 69 | label: '本地', 70 | value: 'local', 71 | }, 72 | { 73 | label: '远程', 74 | value: 'remote', 75 | }, 76 | { 77 | label: 'WebAssembly', 78 | value: 'wasm', 79 | }, 80 | ] 81 | export const synthesizerSide = [ 82 | { 83 | label: '客户端', 84 | value: 'client', 85 | }, 86 | { 87 | label: '服务端', 88 | value: 'server', 89 | }, 90 | ] 91 | 92 | export const YES_NO = [ 93 | { 94 | label: '是', 95 | value: true, 96 | }, 97 | { 98 | label: '否', 99 | value: false, 100 | }, 101 | ] -------------------------------------------------------------------------------- /src/hooks/event/useEventListener.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | 3 | import { ref, watch, unref } from 'vue'; 4 | import { useThrottleFn, useDebounceFn } from '@vueuse/core'; 5 | 6 | export type RemoveEventFn = () => void; 7 | 8 | export interface UseEventParams { 9 | el?: Element | Ref | Window | any; 10 | name: string; 11 | listener: EventListener; 12 | options?: boolean | AddEventListenerOptions; 13 | autoRemove?: boolean; 14 | isDebounce?: boolean; 15 | wait?: number; 16 | } 17 | 18 | export function useEventListener({ 19 | el = window, 20 | name, 21 | listener, 22 | options, 23 | autoRemove = true, 24 | isDebounce = true, 25 | wait = 80, 26 | }: UseEventParams): { removeEvent: RemoveEventFn } { 27 | /* eslint-disable-next-line */ 28 | let remove: RemoveEventFn = () => { 29 | } 30 | const isAddRef = ref(false); 31 | 32 | if (el) { 33 | const element: Ref = ref(el as Element); 34 | 35 | const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait); 36 | const realHandler = wait ? handler : listener; 37 | const removeEventListener = (e: Element) => { 38 | isAddRef.value = true; 39 | e.removeEventListener(name, realHandler, options); 40 | } 41 | const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options); 42 | 43 | const removeWatch = watch( 44 | element, 45 | (v, _ov, cleanUp) => { 46 | if (v) { 47 | !unref(isAddRef) && addEventListener(v); 48 | cleanUp(() => { 49 | autoRemove && removeEventListener(v); 50 | }) 51 | } 52 | }, 53 | { immediate: true } 54 | ); 55 | 56 | remove = () => { 57 | removeEventListener(element.value); 58 | removeWatch(); 59 | } 60 | } 61 | return { removeEvent: remove }; 62 | } 63 | -------------------------------------------------------------------------------- /src/router/modules/dashboard.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { DashboardOutlined } from '@vicons/antd'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | const routeName = 'dashboard'; 7 | 8 | /** 9 | * @param name 路由名称, 必须设置,且不能重名 10 | * @param meta 路由元信息(路由附带扩展信息) 11 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 12 | * @param meta.disabled 禁用整个菜单 13 | * @param meta.title 菜单名称 14 | * @param meta.icon 菜单图标 15 | * @param meta.keepAlive 缓存该路由 16 | * @param meta.sort 排序越小越排前 17 | * */ 18 | const routes: Array = [ 19 | { 20 | path: '/dashboard', 21 | name: routeName, 22 | redirect: '/dashboard/console', 23 | component: Layout, 24 | meta: { 25 | title: 'Dashboard', 26 | icon: renderIcon(DashboardOutlined), 27 | sort: 0, 28 | }, 29 | children: [ 30 | { 31 | path: 'console', 32 | name: `${routeName}_console`, 33 | meta: { 34 | title: '主控台', 35 | affix: true, 36 | }, 37 | component: () => import('@/views/dashboard/console/console.vue'), 38 | }, 39 | // { 40 | // path: 'monitor', 41 | // name: `${ routeName }_monitor`, 42 | // meta: { 43 | // title: '监控页', 44 | // permissions: ['dashboard_monitor'] 45 | // }, 46 | // component: () => import('@/views/dashboard/monitor/monitor.vue') 47 | // }, 48 | // { 49 | // path: 'workplace', 50 | // name: `${routeName}_workplace`, 51 | // meta: { 52 | // title: '工作台', 53 | // keepAlive: true, 54 | // permissions: ['dashboard_workplace'], 55 | // }, 56 | // component: () => import('@/views/dashboard/workplace/workplace.vue'), 57 | // }, 58 | ], 59 | }, 60 | ]; 61 | 62 | export default routes; 63 | -------------------------------------------------------------------------------- /src/hooks/web/usePage.ts: -------------------------------------------------------------------------------- 1 | import type { RouteLocationRaw, Router } from 'vue-router'; 2 | 3 | import { PageEnum } from '@/enums/pageEnum'; 4 | import { RedirectName } from '@/router/constant'; 5 | 6 | import { useRouter } from 'vue-router'; 7 | import { isString } from '@/utils/is'; 8 | import { unref } from 'vue'; 9 | 10 | export type RouteLocationRawEx = Omit & { path: PageEnum }; 11 | 12 | function handleError(e: Error) { 13 | console.error(e); 14 | } 15 | 16 | /** 17 | * 页面切换 18 | */ 19 | export function useGo(_router?: Router) { 20 | let router; 21 | if (!_router) { 22 | router = useRouter(); 23 | } 24 | const { push, replace } = _router || router; 25 | function go(opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME, isReplace = false) { 26 | if (!opt) { 27 | return; 28 | } 29 | if (isString(opt)) { 30 | isReplace ? replace(opt).catch(handleError) : push(opt).catch(handleError); 31 | } else { 32 | const o = opt as RouteLocationRaw; 33 | isReplace ? replace(o).catch(handleError) : push(o).catch(handleError); 34 | } 35 | } 36 | return go; 37 | } 38 | 39 | /** 40 | * 重做当前页面 41 | */ 42 | export const useRedo = (_router?: Router) => { 43 | const { push, currentRoute } = _router || useRouter(); 44 | const { query, params = {}, name, fullPath } = unref(currentRoute.value); 45 | function redo(): Promise { 46 | return new Promise((resolve) => { 47 | if (name === RedirectName) { 48 | resolve(false); 49 | return 50 | } 51 | if (name && Object.keys(params).length > 0) { 52 | params['_redirect_type'] = 'name'; 53 | params['path'] = String(name); 54 | } else { 55 | params['_redirect_type'] = 'path'; 56 | params['path'] = fullPath; 57 | } 58 | push({ name: RedirectName, params, query }).then(() => resolve(true)); 59 | }) 60 | } 61 | return redo; 62 | } 63 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | plugins: ['stylelint-order'], 4 | extends: ['stylelint-config-standard', 'stylelint-config-prettier'], 5 | rules: { 6 | 'selector-pseudo-class-no-unknown': [ 7 | true, 8 | { 9 | ignorePseudoClasses: ['global'], 10 | }, 11 | ], 12 | 'selector-pseudo-element-no-unknown': [ 13 | true, 14 | { 15 | ignorePseudoElements: ['v-deep'], 16 | }, 17 | ], 18 | 'at-rule-no-unknown': [ 19 | true, 20 | { 21 | ignoreAtRules: [ 22 | 'tailwind', 23 | 'apply', 24 | 'variants', 25 | 'responsive', 26 | 'screen', 27 | 'function', 28 | 'if', 29 | 'each', 30 | 'include', 31 | 'mixin', 32 | ], 33 | }, 34 | ], 35 | 'no-empty-source': null, 36 | 'named-grid-areas-no-invalid': null, 37 | 'unicode-bom': 'never', 38 | 'no-descending-specificity': null, 39 | 'font-family-no-missing-generic-family-keyword': null, 40 | 'declaration-colon-space-after': 'always-single-line', 41 | 'declaration-colon-space-before': 'never', 42 | // 'declaration-block-trailing-semicolon': 'always', 43 | 'rule-empty-line-before': [ 44 | 'always', 45 | { 46 | ignore: ['after-comment', 'first-nested'], 47 | }, 48 | ], 49 | 'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }], 50 | 'order/order': [ 51 | [ 52 | 'dollar-variables', 53 | 'custom-properties', 54 | 'at-rules', 55 | 'declarations', 56 | { 57 | type: 'at-rule', 58 | name: 'supports', 59 | }, 60 | { 61 | type: 'at-rule', 62 | name: 'media', 63 | }, 64 | 'rules', 65 | ], 66 | { severity: 'warning' }, 67 | ], 68 | }, 69 | ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'], 70 | }; 71 | -------------------------------------------------------------------------------- /src/router/modules/system.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { OptionsSharp } from '@vicons/ionicons5'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | { 19 | path: '/system', 20 | name: 'System', 21 | redirect: '/system/quota', 22 | component: Layout, 23 | meta: { 24 | title: '系统设置', 25 | icon: renderIcon(OptionsSharp), 26 | sort: 6, 27 | }, 28 | children: [ 29 | { 30 | path: 'storage', 31 | name: 'storage', 32 | meta: { 33 | title: '存储配置', 34 | }, 35 | component: () => import('@/views/system/storage/index.vue'), 36 | }, 37 | { 38 | path: 'audio/asr', 39 | name: 'asr', 40 | meta: { 41 | title: 'ASR语音识别配置', 42 | }, 43 | component: () => import('@/views/system/audio/Asr.vue'), 44 | }, 45 | { 46 | path: 'audio/tts', 47 | name: 'tts', 48 | meta: { 49 | title: 'TTS语音合成配置', 50 | }, 51 | component: () => import('@/views/system/audio/Tts.vue'), 52 | }, 53 | { 54 | path: 'quota', 55 | name: 'TokenQuota', 56 | meta: { 57 | title: '额度配置', 58 | }, 59 | component: () => import('@/views/system/quota/index.vue'), 60 | }, 61 | { 62 | path: 'ratelimit', 63 | name: 'RateLimit', 64 | meta: { 65 | title: '限流策略', 66 | }, 67 | component: () => import('@/views/system/ratelimit/index.vue'), 68 | }, 69 | ], 70 | }, 71 | ]; 72 | 73 | export default routes; 74 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/usePagination.ts: -------------------------------------------------------------------------------- 1 | import type { PaginationProps } from '../types/pagination'; 2 | import type { BasicTableProps } from '../types/table'; 3 | import { computed, unref, ref, ComputedRef, watch } from 'vue'; 4 | 5 | import { isBoolean } from '@/utils/is'; 6 | import { DEFAULTPAGESIZE, PAGESIZES } from '../const'; 7 | 8 | export function usePagination(refProps: ComputedRef) { 9 | const configRef = ref({}); 10 | const show = ref(true); 11 | 12 | watch( 13 | () => unref(refProps).pagination, 14 | (pagination) => { 15 | if (!isBoolean(pagination) && pagination) { 16 | configRef.value = { 17 | ...unref(configRef), 18 | ...(pagination ?? {}), 19 | }; 20 | } 21 | } 22 | ); 23 | 24 | const getPaginationInfo = computed((): PaginationProps | boolean => { 25 | const { pagination } = unref(refProps); 26 | if (!unref(show) || (isBoolean(pagination) && !pagination)) { 27 | return false; 28 | } 29 | return { 30 | page: 1, //当前页 31 | pageSize: DEFAULTPAGESIZE, //分页大小 32 | pageSizes: PAGESIZES, // 每页条数 33 | showSizePicker: true, 34 | showQuickJumper: true, 35 | prefix: (pagingInfo) => `共 ${pagingInfo.itemCount} 条`, // 不需要可以通过 pagination 重置或者删除 36 | ...(isBoolean(pagination) ? {} : pagination), 37 | ...unref(configRef), 38 | }; 39 | }) 40 | 41 | function setPagination(info: Partial) { 42 | const paginationInfo = unref(getPaginationInfo); 43 | configRef.value = { 44 | ...(!isBoolean(paginationInfo) ? paginationInfo : {}), 45 | ...info, 46 | }; 47 | } 48 | 49 | function getPagination() { 50 | return unref(getPaginationInfo); 51 | } 52 | 53 | function getShowPagination() { 54 | return unref(show); 55 | } 56 | 57 | async function setShowPagination(flag: boolean) { 58 | show.value = flag; 59 | } 60 | 61 | return { getPagination, getPaginationInfo, setShowPagination, getShowPagination, setPagination }; 62 | } 63 | -------------------------------------------------------------------------------- /src/views/system/ratelimit/TextRequestRateLimit.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 更新文本请求限流规则 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 68 | -------------------------------------------------------------------------------- /src/views/system/quota/KnowledgeBaseSetting.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 更新提问限额 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 69 | -------------------------------------------------------------------------------- /src/views/system/ratelimit/ImageGenerateRateLimit.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 更新生成图片限流规则 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 68 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig, ConfigEnv } from 'vite'; 2 | import { loadEnv } from 'vite'; 3 | import { resolve } from 'path'; 4 | import { wrapperEnv } from './build/utils'; 5 | import { createVitePlugins } from './build/vite/plugin'; 6 | import { OUTPUT_DIR } from './build/constant'; 7 | import { createProxy } from './build/vite/proxy'; 8 | import pkg from './package.json'; 9 | import { format } from 'date-fns'; 10 | const { dependencies, devDependencies, name, version } = pkg; 11 | 12 | const __APP_INFO__ = { 13 | pkg: { dependencies, devDependencies, name, version }, 14 | lastBuildTime: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), 15 | }; 16 | 17 | function pathResolve(dir: string) { 18 | return resolve(process.cwd(), '.', dir); 19 | } 20 | 21 | export default ({ command, mode }: ConfigEnv): UserConfig => { 22 | const root = process.cwd(); 23 | const env = loadEnv(mode, root); 24 | const viteEnv = wrapperEnv(env); 25 | const { VITE_PUBLIC_PATH, VITE_PORT, VITE_GLOB_PROD_MOCK, VITE_PROXY } = viteEnv; 26 | const prodMock = VITE_GLOB_PROD_MOCK; 27 | const isBuild = command === 'build'; 28 | return { 29 | base: VITE_PUBLIC_PATH, 30 | esbuild: {}, 31 | resolve: { 32 | alias: [ 33 | { 34 | find: /\/#\//, 35 | replacement: pathResolve('types') + '/', 36 | }, 37 | { 38 | find: '@', 39 | replacement: pathResolve('src') + '/', 40 | }, 41 | ], 42 | dedupe: ['vue'], 43 | }, 44 | plugins: createVitePlugins(viteEnv, isBuild, prodMock), 45 | define: { 46 | __APP_INFO__: JSON.stringify(__APP_INFO__), 47 | }, 48 | server: { 49 | host: true, 50 | port: VITE_PORT, 51 | proxy: createProxy(VITE_PROXY), 52 | }, 53 | optimizeDeps: { 54 | include: [], 55 | exclude: ['vue-demi'], 56 | }, 57 | build: { 58 | target: 'es2015', 59 | cssTarget: 'chrome80', 60 | outDir: OUTPUT_DIR, 61 | reportCompressedSize: false, 62 | chunkSizeWarningLimit: 2000, 63 | }, 64 | }; 65 | }; 66 | -------------------------------------------------------------------------------- /src/layout/components/Main/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/views/system/quota/RequestSetting.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 更新请求限额 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 70 | -------------------------------------------------------------------------------- /src/components/Form/src/types/form.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from './index'; 2 | import type { CSSProperties } from 'vue'; 3 | import type { GridProps, GridItemProps } from 'naive-ui/lib/grid'; 4 | import type { ButtonProps } from 'naive-ui/lib/button'; 5 | 6 | export interface FormSchema { 7 | field: string; 8 | label: string; 9 | labelMessage?: string; 10 | labelMessageStyle?: object | string; 11 | defaultValue?: any; 12 | component?: ComponentType; 13 | componentProps?: object; 14 | slot?: string; 15 | rules?: object | object[]; 16 | giProps?: GridItemProps; 17 | isFull?: boolean; 18 | suffix?: string; 19 | } 20 | 21 | export interface FormProps { 22 | model?: Recordable; 23 | labelWidth?: number | string; 24 | schemas?: FormSchema[]; 25 | inline: boolean; 26 | layout?: string; 27 | size: string; 28 | labelPlacement: string; 29 | isFull: boolean; 30 | showActionButtonGroup?: boolean; 31 | showResetButton?: boolean; 32 | resetButtonOptions?: Partial; 33 | showSubmitButton?: boolean; 34 | showAdvancedButton?: boolean; 35 | submitButtonOptions?: Partial; 36 | submitButtonText?: string; 37 | resetButtonText?: string; 38 | gridProps?: GridProps; 39 | giProps?: GridItemProps; 40 | resetFunc?: () => Promise; 41 | submitFunc?: () => Promise; 42 | submitOnReset?: boolean; 43 | baseGridStyle?: CSSProperties; 44 | collapsedRows?: number; 45 | } 46 | 47 | export interface FormActionType { 48 | submit: () => Promise; 49 | setProps: (formProps: Partial) => Promise; 50 | setSchema: (schemaProps: Partial) => Promise; 51 | setFieldsValue: (values: Recordable) => void; 52 | clearValidate: (name?: string | string[]) => Promise; 53 | getFieldsValue: () => Recordable; 54 | resetFields: () => Promise; 55 | validate: (nameList?: any[]) => Promise; 56 | setLoading: (status: boolean) => void; 57 | } 58 | 59 | export type RegisterFn = (formInstance: FormActionType) => void; 60 | 61 | export type UseFormReturnType = [RegisterFn, FormActionType]; 62 | -------------------------------------------------------------------------------- /src/components/Form/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { CSSProperties, PropType } from 'vue'; 2 | import { FormSchema } from './types/form'; 3 | import type { GridProps, GridItemProps } from 'naive-ui/lib/grid'; 4 | import type { ButtonProps } from 'naive-ui/lib/button'; 5 | import { propTypes } from '@/utils/propTypes'; 6 | export const basicProps = { 7 | // 标签宽度 固定宽度 8 | labelWidth: { 9 | type: [Number, String] as PropType, 10 | default: 80, 11 | }, 12 | // 表单配置规则 13 | schemas: { 14 | type: [Array] as PropType, 15 | default: () => [], 16 | }, 17 | //布局方式 18 | layout: { 19 | type: String, 20 | default: 'inline', 21 | }, 22 | //是否展示为行内表单 23 | inline: { 24 | type: Boolean, 25 | default: false, 26 | }, 27 | //大小 28 | size: { 29 | type: String, 30 | default: 'medium', 31 | }, 32 | //标签位置 33 | labelPlacement: { 34 | type: String, 35 | default: 'left', 36 | }, 37 | //组件是否width 100% 38 | isFull: { 39 | type: Boolean, 40 | default: true, 41 | }, 42 | //是否显示操作按钮(查询/重置) 43 | showActionButtonGroup: propTypes.bool.def(true), 44 | // 显示重置按钮 45 | showResetButton: propTypes.bool.def(true), 46 | //重置按钮配置 47 | resetButtonOptions: Object as PropType>, 48 | // 显示确认按钮 49 | showSubmitButton: propTypes.bool.def(true), 50 | // 确认按钮配置 51 | submitButtonOptions: Object as PropType>, 52 | //展开收起按钮 53 | showAdvancedButton: propTypes.bool.def(true), 54 | // 确认按钮文字 55 | submitButtonText: { 56 | type: String, 57 | default: '查询', 58 | }, 59 | //重置按钮文字 60 | resetButtonText: { 61 | type: String, 62 | default: '重置', 63 | }, 64 | //grid 配置 65 | gridProps: Object as PropType, 66 | //gi配置 67 | giProps: Object as PropType, 68 | //grid 样式 69 | baseGridStyle: { 70 | type: Object as PropType, 71 | }, 72 | //是否折叠 73 | collapsed: { 74 | type: Boolean, 75 | default: false, 76 | }, 77 | //默认展示的行数 78 | collapsedRows: { 79 | type: Number, 80 | default: 1, 81 | }, 82 | }; 83 | -------------------------------------------------------------------------------- /src/views/system/quota/TokenSetting.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 更新Token限额 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 71 | -------------------------------------------------------------------------------- /src/views/system/quota/ImageGenerateSetting.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 更新生成图片数量限额 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 70 | -------------------------------------------------------------------------------- /mock/system/menu.ts: -------------------------------------------------------------------------------- 1 | import { resultSuccess } from '../_util'; 2 | 3 | const menuList = () => { 4 | const result: any[] = [ 5 | { 6 | label: 'Dashboard', 7 | key: 'dashboard', 8 | type: 1, 9 | subtitle: 'dashboard', 10 | openType: 1, 11 | auth: 'dashboard', 12 | path: '/dashboard', 13 | children: [ 14 | { 15 | label: '主控台', 16 | key: 'console', 17 | type: 1, 18 | subtitle: 'console', 19 | openType: 1, 20 | auth: 'console', 21 | path: '/dashboard/console', 22 | }, 23 | { 24 | label: '工作台', 25 | key: 'workplace', 26 | type: 1, 27 | subtitle: 'workplace', 28 | openType: 1, 29 | auth: 'workplace', 30 | path: '/dashboard/workplace', 31 | }, 32 | ], 33 | }, 34 | { 35 | label: '表单管理', 36 | key: 'form', 37 | type: 1, 38 | subtitle: 'form', 39 | openType: 1, 40 | auth: 'form', 41 | path: '/form', 42 | children: [ 43 | { 44 | label: '基础表单', 45 | key: 'basic-form', 46 | type: 1, 47 | subtitle: 'basic-form', 48 | openType: 1, 49 | auth: 'basic-form', 50 | path: '/form/basic-form', 51 | }, 52 | { 53 | label: '分步表单', 54 | key: 'step-form', 55 | type: 1, 56 | subtitle: 'step-form', 57 | openType: 1, 58 | auth: 'step-form', 59 | path: '/form/step-form', 60 | }, 61 | { 62 | label: '表单详情', 63 | key: 'detail', 64 | type: 1, 65 | subtitle: 'detail', 66 | openType: 1, 67 | auth: 'detail', 68 | path: '/form/detail', 69 | }, 70 | ], 71 | }, 72 | ]; 73 | 74 | return result; 75 | } 76 | 77 | export default [ 78 | { 79 | url: '/api/admin/menu/list', 80 | timeout: 1000, 81 | method: 'get', 82 | response: () => { 83 | const list = menuList(); 84 | return resultSuccess({ 85 | list, 86 | }); 87 | }, 88 | }, 89 | ]; 90 | -------------------------------------------------------------------------------- /src/assets/images/nav-horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 27 | -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios' 2 | import { BasicResponseModel } from '/#/global' 3 | 4 | /** 5 | * @description: 用户登录 6 | */ 7 | function login(params) { 8 | return http.request( 9 | { 10 | url: '/auth/login', 11 | method: 'POST', 12 | params, 13 | }, 14 | { 15 | isShowMessage: false, 16 | } 17 | ) 18 | } 19 | 20 | function search(data, params: { current: number, size: number }) { 21 | return http.request({ 22 | url: `/admin/user/search?currentPage=${params.current}&pageSize=${params.size}`, 23 | method: 'post', 24 | data, 25 | }) 26 | } 27 | 28 | /** 29 | * @description: 获取用户信息 30 | */ 31 | function getUserInfo(uuid: string) { 32 | return http.request({ 33 | url: `/admin/user/info/${uuid}`, 34 | method: 'get', 35 | }) 36 | } 37 | 38 | /** 39 | * @description: 用户修改密码 40 | */ 41 | function changePassword(params, uid) { 42 | return http.request( 43 | { 44 | url: `/admin/user/u${uid}/changepw`, 45 | method: 'POST', 46 | params, 47 | }, 48 | { 49 | isTransformResponse: false, 50 | } 51 | ) 52 | } 53 | 54 | function active(uuid: string) { 55 | return http.request( 56 | { 57 | url: `/admin/user/active/${uuid}`, 58 | method: 'POST', 59 | } 60 | ) 61 | } 62 | 63 | function freeze(uuid: string) { 64 | return http.request( 65 | { 66 | url: `/admin/user/freeze/${uuid}`, 67 | method: 'POST', 68 | } 69 | ) 70 | } 71 | 72 | function addOne(data) { 73 | return http.request({ 74 | url: '/admin/user/addOne', 75 | method: 'post', 76 | data, 77 | }) 78 | } 79 | 80 | function edit(data) { 81 | return http.request({ 82 | url: '/admin/user/edit', 83 | method: 'post', 84 | data, 85 | }) 86 | } 87 | 88 | /** 89 | * @description: 用户登出 90 | */ 91 | function logout() { 92 | return http.request({ 93 | url: '/user/logout', 94 | method: 'POST', 95 | }) 96 | } 97 | 98 | export default { 99 | login, 100 | search, 101 | active, 102 | freeze, 103 | addOne, 104 | edit, 105 | getUserInfo, 106 | changePassword, 107 | logout, 108 | } 109 | -------------------------------------------------------------------------------- /src/api/workflow.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios' 2 | 3 | function componentSearch(data: { isEnable?: boolean }, params: { current: number, size: number }) { 4 | return http.request({ 5 | url: `/admin/workflow/component/search?currentPage=${params.current}&pageSize=${params.size}`, 6 | method: 'post', 7 | data, 8 | }) 9 | } 10 | 11 | function componentAddOrUpdate(data: { uuid: string; title: string; remark: string; displayOrder: number; isEnable: boolean }) { 12 | return http.request({ 13 | url: '/admin/workflow/component/addOrUpdate', 14 | method: 'post', 15 | data 16 | }) 17 | } 18 | 19 | function componentEnable(params: { uuid: string; isEnable: boolean }) { 20 | return http.request({ 21 | url: `/admin/workflow/component/enable?uuid=${params.uuid}&isEnable=${params.isEnable}`, 22 | method: 'post', 23 | }) 24 | } 25 | 26 | function componentDel(data: { uuid: string }) { 27 | return http.request({ 28 | url: `/admin/workflow/component/del/${data.uuid}`, 29 | method: 'post', 30 | }) 31 | } 32 | 33 | function search(data: { title?: string, isPublic?: boolean }, params: { current: number, size: number }) { 34 | return http.request({ 35 | url: `/admin/workflow/search?currentPage=${params.current}&pageSize=${params.size}`, 36 | method: 'post', 37 | data 38 | }) 39 | } 40 | 41 | function updateBaseInfo(data: { uuid: string; title: string; remark: string }) { 42 | return http.request({ 43 | url: '/workflow/base-info/update', 44 | method: 'post', 45 | data 46 | }) 47 | } 48 | 49 | function del(uuid: string) { 50 | return http.request({ 51 | url: `/workflow/del/${uuid}`, 52 | method: 'post', 53 | }) 54 | } 55 | 56 | function setPublic(uuid: string, isPublic: boolean) { 57 | return http.request({ 58 | url: `/workflow/set-public/${uuid}?isPublic=${isPublic}`, 59 | method: 'post', 60 | }) 61 | } 62 | 63 | function setEnable(params: { uuid: string; isEnable: boolean }) { 64 | return http.request({ 65 | url: '/admin/workflow/enable', 66 | method: 'post', 67 | params 68 | }) 69 | } 70 | 71 | export default { 72 | componentSearch, 73 | componentAddOrUpdate, 74 | componentEnable, 75 | componentDel, 76 | search, 77 | updateBaseInfo, 78 | del, 79 | setPublic, 80 | setEnable, 81 | } 82 | -------------------------------------------------------------------------------- /src/utils/downloadFile.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 根据文件url获取文件名 3 | * @param url 文件url 4 | */ 5 | function getFileName(url) { 6 | const num = url.lastIndexOf('/') + 1; 7 | let fileName = url.substring(num); 8 | //把参数和文件名分割开 9 | fileName = decodeURI(fileName.split('?')[0]); 10 | return fileName; 11 | } 12 | 13 | /** 14 | * 根据文件地址下载文件 15 | * @param {*} sUrl 16 | */ 17 | export function downloadByUrl({ 18 | url, 19 | target = '_blank', 20 | fileName, 21 | }: { 22 | url: string; 23 | target?: '_self' | '_blank'; 24 | fileName?: string; 25 | }): Promise { 26 | // 是否同源 27 | const isSameHost = new URL(url).host == location.host; 28 | return new Promise((resolve, reject) => { 29 | if (isSameHost) { 30 | const link = document.createElement('a'); 31 | link.href = url; 32 | link.target = target; 33 | 34 | if (link.download !== undefined) { 35 | link.download = fileName || getFileName(url); 36 | } 37 | 38 | if (document.createEvent) { 39 | const e = document.createEvent('MouseEvents'); 40 | e.initEvent('click', true, true); 41 | link.dispatchEvent(e); 42 | return resolve(true); 43 | } 44 | 45 | if (url.indexOf('?') === -1) { 46 | url += '?download'; 47 | } 48 | 49 | window.open(url, target); 50 | return resolve(true); 51 | } else { 52 | const canvas = document.createElement('canvas'); 53 | const img = document.createElement('img'); 54 | img.setAttribute('crossOrigin', 'Anonymous'); 55 | img.src = url; 56 | img.onload = () => { 57 | canvas.width = img.width; 58 | canvas.height = img.height; 59 | const context = canvas.getContext('2d')!; 60 | context.drawImage(img, 0, 0, img.width, img.height); 61 | // window.navigator.msSaveBlob(canvas.msToBlob(),'image.jpg'); 62 | // saveAs(imageDataUrl, '附件'); 63 | canvas.toBlob((blob) => { 64 | const link = document.createElement('a'); 65 | link.href = window.URL.createObjectURL(blob); 66 | link.download = getFileName(url); 67 | link.click(); 68 | URL.revokeObjectURL(link.href); 69 | resolve(true); 70 | }, 'image/jpeg'); 71 | } 72 | img.onerror = (e) => reject(e); 73 | } 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /src/store/modules/tabsView.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { RouteLocationNormalized } from 'vue-router'; 3 | 4 | // 不需要出现在标签页中的路由 5 | const whiteList = ['Redirect', 'login']; 6 | 7 | export type RouteItem = Partial & { 8 | fullPath: string; 9 | path: string; 10 | name: string; 11 | hash: string; 12 | meta: object; 13 | params: object; 14 | query: object; 15 | }; 16 | 17 | export type ITabsViewState = { 18 | tabsList: RouteItem[]; // 标签页 19 | }; 20 | 21 | //保留固定路由 22 | function retainAffixRoute(list: any[]) { 23 | return list.filter((item) => item?.meta?.affix ?? false); 24 | } 25 | 26 | export const useTabsViewStore = defineStore({ 27 | id: 'app-tabs-view', 28 | state: (): ITabsViewState => ({ 29 | tabsList: [], 30 | }), 31 | getters: {}, 32 | actions: { 33 | initTabs(routes: RouteItem[]) { 34 | // 初始化标签页 35 | this.tabsList = routes; 36 | }, 37 | addTab(route: RouteItem): boolean { 38 | // 添加标签页 39 | if (whiteList.includes(route.name)) return false; 40 | const isExists = this.tabsList.some((item) => item.fullPath == route.fullPath); 41 | if (!isExists) { 42 | this.tabsList.push(route); 43 | } 44 | return true; 45 | }, 46 | closeLeftTabs(route: RouteItem) { 47 | // 关闭左侧 48 | const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath); 49 | this.tabsList = this.tabsList.filter((item, i) => i >= index || (item?.meta?.affix ?? false)); 50 | }, 51 | closeRightTabs(route: RouteItem) { 52 | // 关闭右侧 53 | const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath); 54 | this.tabsList = this.tabsList.filter((item, i) => i <= index || (item?.meta?.affix ?? false)); 55 | }, 56 | closeOtherTabs(route: RouteItem) { 57 | // 关闭其他 58 | this.tabsList = this.tabsList.filter( 59 | (item) => item.fullPath == route.fullPath || (item?.meta?.affix ?? false) 60 | ); 61 | }, 62 | closeCurrentTab(route: RouteItem) { 63 | // 关闭当前页 64 | const index = this.tabsList.findIndex((item) => item.fullPath == route.fullPath); 65 | this.tabsList.splice(index, 1); 66 | }, 67 | closeAllTabs() { 68 | // 关闭全部 69 | this.tabsList = retainAffixRoute(this.tabsList); 70 | }, 71 | }, 72 | }); 73 | -------------------------------------------------------------------------------- /src/views/knowledge-base/columns.ts: -------------------------------------------------------------------------------- 1 | import { BasicColumn } from '@/components/Table' 2 | import { wrapTableTitle } from '@/utils' 3 | 4 | export interface KbInfoData { 5 | id: string 6 | uuid: string 7 | title: string 8 | remark: string 9 | ownerUuid: string 10 | ownerName: string 11 | isPublic: boolean 12 | starCount: number 13 | itemCount: number 14 | embeddingCount: number 15 | createTime: string 16 | updateTime: string 17 | } 18 | 19 | export const columns: BasicColumn[] = [ 20 | { 21 | title: 'id', 22 | key: 'id', 23 | width: 50, 24 | }, 25 | { 26 | title: '名称', 27 | key: 'title', 28 | width: 150, 29 | }, 30 | { 31 | title: '所属用户', 32 | key: 'ownerName', 33 | width: 150, 34 | }, 35 | { 36 | title: '知识点', 37 | key: 'itemCount', 38 | width: 80, 39 | }, 40 | { 41 | title: '向量数', 42 | key: 'embeddingCount', 43 | width: 80, 44 | }, 45 | { 46 | title: '点赞数', 47 | key: 'starCount', 48 | width: 80, 49 | }, 50 | { 51 | title: '是否公开', 52 | key: 'isPublic', 53 | width: 80, 54 | render(row) { 55 | return row.isPublic ? '是' : '否' 56 | }, 57 | }, 58 | { 59 | key: 'ingestMaxOverlap', 60 | width: 100, 61 | title(column) { 62 | return wrapTableTitle('重叠数量(文档切块时)') 63 | } 64 | }, 65 | { 66 | key: 'ingestModelName', 67 | width: 160, 68 | title(column) { 69 | return wrapTableTitle('模型(文档切块时)') 70 | } 71 | }, 72 | { 73 | key: 'retrieveMaxResults', 74 | width: 100, 75 | title(column) { 76 | return wrapTableTitle('文档召回最大数量') 77 | } 78 | }, 79 | { 80 | key: 'retrieveMinScore', 81 | width: 100, 82 | title(column) { 83 | return wrapTableTitle('文档召回最小分数') 84 | } 85 | }, 86 | { 87 | key: 'queryLlmTemperature', 88 | width: 120, 89 | title(column) { 90 | return wrapTableTitle('大模型输出内容的创造性') 91 | } 92 | }, 93 | { 94 | title: '请求时的系统提示词', 95 | key: 'querySystemMessage', 96 | width: 150, 97 | }, 98 | { 99 | title: '创建时间', 100 | key: 'createTime', 101 | width: 180, 102 | }, 103 | { 104 | title: '更新时间', 105 | key: 'updateTime', 106 | width: 180, 107 | }, 108 | 109 | ] 110 | -------------------------------------------------------------------------------- /src/utils/env.ts: -------------------------------------------------------------------------------- 1 | import type { GlobEnvConfig } from '/#/config'; 2 | 3 | import { warn } from '@/utils/log'; 4 | import pkg from '../../package.json'; 5 | import { getConfigFileName } from '../../build/getConfigFileName'; 6 | 7 | export function getCommonStoragePrefix() { 8 | const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig(); 9 | return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase(); 10 | } 11 | 12 | // Generate cache key according to version 13 | export function getStorageShortName() { 14 | return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase(); 15 | } 16 | 17 | export function getAppEnvConfig() { 18 | const ENV_NAME = getConfigFileName(import.meta.env); 19 | 20 | const ENV = (import.meta.env.DEV 21 | ? // Get the global configuration (the configuration will be extracted independently when packaging) 22 | (import.meta.env as unknown as GlobEnvConfig) 23 | : window[ENV_NAME as any]) as unknown as GlobEnvConfig; 24 | 25 | const { 26 | VITE_GLOB_APP_TITLE, 27 | VITE_GLOB_API_URL, 28 | VITE_GLOB_APP_SHORT_NAME, 29 | VITE_GLOB_API_URL_PREFIX, 30 | VITE_GLOB_UPLOAD_URL, 31 | VITE_GLOB_PROD_MOCK, 32 | VITE_GLOB_IMG_URL, 33 | } = ENV; 34 | 35 | if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) { 36 | warn( 37 | 'VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.' 38 | ); 39 | } 40 | 41 | return { 42 | VITE_GLOB_APP_TITLE, 43 | VITE_GLOB_API_URL, 44 | VITE_GLOB_APP_SHORT_NAME, 45 | VITE_GLOB_API_URL_PREFIX, 46 | VITE_GLOB_UPLOAD_URL, 47 | VITE_GLOB_PROD_MOCK, 48 | VITE_GLOB_IMG_URL, 49 | }; 50 | } 51 | 52 | /** 53 | * @description: Development model 54 | */ 55 | export const devMode = 'development'; 56 | 57 | /** 58 | * @description: Production mode 59 | */ 60 | export const prodMode = 'production'; 61 | 62 | /** 63 | * @description: Get environment variables 64 | * @returns: 65 | * @example: 66 | */ 67 | export function getEnv(): string { 68 | return import.meta.env.MODE; 69 | } 70 | 71 | /** 72 | * @description: Is it a development mode 73 | * @returns: 74 | * @example: 75 | */ 76 | export function isDevMode(): boolean { 77 | return import.meta.env.DEV; 78 | } 79 | 80 | /** 81 | * @description: Is it a production mode 82 | * @returns: 83 | * @example: 84 | */ 85 | export function isProdMode(): boolean { 86 | return import.meta.env.PROD; 87 | } 88 | -------------------------------------------------------------------------------- /src/store/modules/projectSetting.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import projectSetting from '@/settings/projectSetting'; 3 | import type { IHeaderSetting, IMenuSetting, IMultiTabsSetting, ICrumbsSetting } from '/#/config'; 4 | 5 | const { 6 | navMode, 7 | navTheme, 8 | isMobile, 9 | headerSetting, 10 | showFooter, 11 | menuSetting, 12 | multiTabsSetting, 13 | crumbsSetting, 14 | permissionMode, 15 | isPageAnimate, 16 | pageAnimateType, 17 | } = projectSetting; 18 | 19 | interface ProjectSettingState { 20 | navMode: string; //导航模式 21 | navTheme: string; //导航风格 22 | headerSetting: IHeaderSetting; //顶部设置 23 | showFooter: boolean; //页脚 24 | menuSetting: IMenuSetting; //多标签 25 | multiTabsSetting: IMultiTabsSetting; //多标签 26 | crumbsSetting: ICrumbsSetting; //面包屑 27 | permissionMode: string; //权限模式 28 | isPageAnimate: boolean; //是否开启路由动画 29 | pageAnimateType: string; //路由动画类型 30 | isMobile: boolean; // 是否处于移动端模式 31 | } 32 | 33 | export const useProjectSettingStore = defineStore({ 34 | id: 'app-project-setting', 35 | state: (): ProjectSettingState => ({ 36 | navMode: navMode, 37 | navTheme, 38 | isMobile, 39 | headerSetting, 40 | showFooter, 41 | menuSetting, 42 | multiTabsSetting, 43 | crumbsSetting, 44 | permissionMode, 45 | isPageAnimate, 46 | pageAnimateType, 47 | }), 48 | getters: { 49 | getNavMode(): string { 50 | return this.navMode; 51 | }, 52 | getNavTheme(): string { 53 | return this.navTheme; 54 | }, 55 | getIsMobile(): boolean { 56 | return this.isMobile; 57 | }, 58 | getHeaderSetting(): object { 59 | return this.headerSetting; 60 | }, 61 | getShowFooter(): boolean { 62 | return this.showFooter; 63 | }, 64 | getMenuSetting(): object { 65 | return this.menuSetting; 66 | }, 67 | getMultiTabsSetting(): object { 68 | return this.multiTabsSetting; 69 | }, 70 | getCrumbsSetting(): object { 71 | return this.crumbsSetting; 72 | }, 73 | getPermissionMode(): string { 74 | return this.permissionMode; 75 | }, 76 | getIsPageAnimate(): boolean { 77 | return this.isPageAnimate; 78 | }, 79 | getPageAnimateType(): string { 80 | return this.pageAnimateType; 81 | }, 82 | }, 83 | actions: { 84 | setNavTheme(value: string): void { 85 | this.navTheme = value; 86 | }, 87 | setIsMobile(value: boolean): void { 88 | this.isMobile = value; 89 | }, 90 | }, 91 | }); 92 | -------------------------------------------------------------------------------- /src/views/system/storage/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 当前存储位置:{{ selectedStorageLocation === 1 ? '本地存储' : '阿里云OSS' }} 5 | 6 | 7 | 8 | 9 | 11 | {{ item.name }} 12 | {{ item.desc }} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 63 | 87 | -------------------------------------------------------------------------------- /src/store/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { store } from '@/store'; 3 | import { ACCESS_TOKEN, CURRENT_USER, IS_SCREENLOCKED } from '@/store/mutation-types'; 4 | 5 | import userApi from '@/api/user'; 6 | import { storage } from '@/utils/Storage'; 7 | import { UserInfoType, IUserState } from '/#/user' 8 | 9 | 10 | export const useUserStore = defineStore({ 11 | id: 'app-user', 12 | state: (): IUserState => ({ 13 | token: storage.get(ACCESS_TOKEN, ''), 14 | name: '', 15 | welcome: '', 16 | avatar: '', 17 | permissions: [], 18 | info: storage.get(CURRENT_USER, {}), 19 | }), 20 | getters: { 21 | getToken(): string { 22 | return this.token; 23 | }, 24 | getAvatar(): string { 25 | return this.avatar; 26 | }, 27 | getPermissions(): [any][] { 28 | return this.permissions; 29 | }, 30 | getUserInfo(): UserInfoType { 31 | return this.info; 32 | }, 33 | }, 34 | actions: { 35 | setToken(token: string) { 36 | this.token = token; 37 | }, 38 | setAvatar(avatar: string) { 39 | this.avatar = avatar; 40 | }, 41 | setPermissions(permissions) { 42 | this.permissions = permissions; 43 | }, 44 | setUserInfo(info: UserInfoType) { 45 | this.info = info; 46 | }, 47 | // 登录 48 | async login(params: any) { 49 | this.clearToken() 50 | const response = await userApi.login(params); 51 | const { data, success, message, code } = response; 52 | if (success) { 53 | const ex = 7 * 24 * 60 * 60; 54 | storage.set(ACCESS_TOKEN, data.token, ex); 55 | storage.set(CURRENT_USER, data, ex); 56 | storage.set(IS_SCREENLOCKED, false); 57 | storage.setCookie('Authorization', data.token) 58 | // document.cookie = `Authorization=${data.token}` 59 | this.setToken(data.token); 60 | this.setUserInfo(data); 61 | } else { 62 | console.log(`login fail, erroCode:${code},errorMessage:${message}`) 63 | } 64 | return response; 65 | }, 66 | 67 | // 登出 68 | async logout() { 69 | try{ 70 | await userApi.logout() 71 | }catch(e){ 72 | console.error(e) 73 | }finally{ 74 | this.clearToken() 75 | } 76 | }, 77 | clearToken(){ 78 | this.setPermissions([]); 79 | this.setToken('') 80 | this.setUserInfo({ name: '', email: '', uuid: '' }); 81 | storage.clear(); 82 | storage.clearCookie() 83 | } 84 | }, 85 | }); 86 | 87 | // Need to be used outside the setup 88 | export function useUser() { 89 | return useUserStore(store); 90 | } 91 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { defineConfig } = require('eslint-define-config'); 3 | module.exports = defineConfig({ 4 | root: true, 5 | env: { 6 | browser: true, 7 | node: true, 8 | es6: true, 9 | }, 10 | parser: 'vue-eslint-parser', 11 | parserOptions: { 12 | parser: '@typescript-eslint/parser', 13 | ecmaVersion: 2020, 14 | sourceType: 'module', 15 | jsxPragma: 'React', 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | }, 20 | extends: [ 21 | 'plugin:vue/vue3-recommended', 22 | 'plugin:@typescript-eslint/recommended', 23 | 'prettier', 24 | 'plugin:prettier/recommended', 25 | ], 26 | rules: { 27 | 'semi': [2, 'never'], 28 | 'semi-spacing': [2, { 29 | 'before': false, 30 | 'after': true 31 | }], 32 | 'quotes': [2, 'single', { 33 | 'avoidEscape': true, 34 | 'allowTemplateLiterals': true 35 | }], 36 | 'vue/script-setup-uses-vars': 'error', 37 | 'vue/multi-word-component-names': 'off', 38 | '@typescript-eslint/ban-ts-ignore': 'off', 39 | '@typescript-eslint/explicit-function-return-type': 'off', 40 | '@typescript-eslint/no-explicit-any': 'off', 41 | '@typescript-eslint/no-var-requires': 'off', 42 | '@typescript-eslint/no-empty-function': 'off', 43 | 'vue/custom-event-name-casing': 'off', 44 | 'no-use-before-define': 'off', 45 | '@typescript-eslint/no-use-before-define': 'off', 46 | '@typescript-eslint/ban-ts-comment': 'off', 47 | '@typescript-eslint/ban-types': 'off', 48 | '@typescript-eslint/no-non-null-assertion': 'off', 49 | '@typescript-eslint/explicit-module-boundary-types': 'off', 50 | '@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '.*', args: 'none' }], 51 | 'no-unused-vars': [ 52 | 'error', 53 | // we are only using this rule to check for unused arguments since TS 54 | // catches unused variables but not args. 55 | { varsIgnorePattern: '.*', args: 'none' }, 56 | ], 57 | 'space-before-function-paren': 'off', 58 | 59 | 'vue/attributes-order': 'off', 60 | 'vue/one-component-per-file': 'off', 61 | 'vue/html-closing-bracket-newline': 'off', 62 | 'vue/max-attributes-per-line': 'off', 63 | 'vue/multiline-html-element-content-newline': 'off', 64 | 'vue/singleline-html-element-content-newline': 'off', 65 | 'vue/attribute-hyphenation': 'off', 66 | 'vue/require-default-prop': 'off', 67 | 'vue/html-self-closing': [ 68 | 'error', 69 | { 70 | html: { 71 | void: 'always', 72 | normal: 'never', 73 | component: 'always', 74 | }, 75 | svg: 'always', 76 | math: 'always', 77 | }, 78 | ], 79 | }, 80 | }); 81 | -------------------------------------------------------------------------------- /src/hooks/useBattery.ts: -------------------------------------------------------------------------------- 1 | import { computed, onMounted, reactive, toRefs } from 'vue'; 2 | 3 | interface Battery { 4 | charging: boolean; // 当前电池是否正在充电 5 | chargingTime: number; // 距离充电完毕还需多少秒,如果为0则充电完毕 6 | dischargingTime: number; // 代表距离电池耗电至空且挂起需要多少秒 7 | level: number; // 代表电量的放大等级,这个值在 0.0 至 1.0 之间 8 | [key: string]: any; 9 | } 10 | 11 | export const useBattery = () => { 12 | const state = reactive({ 13 | battery: { 14 | charging: false, 15 | chargingTime: 0, 16 | dischargingTime: 0, 17 | level: 100, 18 | }, 19 | }); 20 | 21 | // 更新电池使用状态 22 | const updateBattery = (target) => { 23 | for (const key in state.battery) { 24 | state.battery[key] = target[key]; 25 | } 26 | state.battery.level = state.battery.level * 100; 27 | } 28 | 29 | // 计算电池剩余可用时间 30 | const calcDischargingTime = computed(() => { 31 | const hour = state.battery.dischargingTime / 3600; 32 | const minute = (state.battery.dischargingTime / 60) % 60; 33 | return `${~~hour}小时${~~minute}分钟`; 34 | }) 35 | 36 | // 计算电池充满剩余时间 37 | const calcChargingTime = computed(() => { 38 | console.log(state.battery); 39 | const hour = state.battery.chargingTime / 3600; 40 | const minute = (state.battery.chargingTime / 60) % 60; 41 | return `${~~hour}小时${~~minute}分钟`; 42 | }) 43 | 44 | // 电池状态 45 | const batteryStatus = computed(() => { 46 | if (state.battery.charging && state.battery.level >= 100) { 47 | return '已充满'; 48 | } else if (state.battery.charging) { 49 | return '充电中'; 50 | } else { 51 | return '已断开电源'; 52 | } 53 | }); 54 | 55 | onMounted(async () => { 56 | const BatteryManager: Battery = await (window.navigator as any).getBattery(); 57 | updateBattery(BatteryManager); 58 | 59 | // 电池充电状态更新时被调用 60 | BatteryManager.onchargingchange = ({ target }) => { 61 | updateBattery(target); 62 | } 63 | // 电池充电时间更新时被调用 64 | BatteryManager.onchargingtimechange = ({ target }) => { 65 | updateBattery(target); 66 | } 67 | // 电池断开充电时间更新时被调用 68 | BatteryManager.ondischargingtimechange = ({ target }) => { 69 | updateBattery(target); 70 | } 71 | // 电池电量更新时被调用 72 | BatteryManager.onlevelchange = ({ target }) => { 73 | updateBattery(target); 74 | } 75 | 76 | // new Intl.DateTimeFormat('zh', { 77 | // year: 'numeric', 78 | // month: '2-digit', 79 | // day: '2-digit', 80 | // hour: '2-digit', 81 | // minute: '2-digit', 82 | // second: '2-digit', 83 | // hour12: false 84 | // }).format(new Date()) 85 | }); 86 | 87 | return { 88 | ...toRefs(state), 89 | batteryStatus, 90 | calcDischargingTime, 91 | calcChargingTime, 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /src/hooks/event/useBreakpoint.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, ComputedRef, unref } from 'vue'; 2 | import { useEventListener } from '@/hooks/event/useEventListener'; 3 | import { screenMap, sizeEnum, screenEnum } from '@/enums/breakpointEnum'; 4 | 5 | let globalScreenRef: ComputedRef; 6 | let globalWidthRef: ComputedRef; 7 | let globalRealWidthRef: ComputedRef; 8 | 9 | export interface CreateCallbackParams { 10 | screen: ComputedRef; 11 | width: ComputedRef; 12 | realWidth: ComputedRef; 13 | screenEnum: typeof screenEnum; 14 | screenMap: Map; 15 | sizeEnum: typeof sizeEnum; 16 | } 17 | 18 | export function useBreakpoint() { 19 | return { 20 | screenRef: computed(() => unref(globalScreenRef)), 21 | widthRef: globalWidthRef, 22 | screenEnum, 23 | realWidthRef: globalRealWidthRef, 24 | }; 25 | } 26 | 27 | // Just call it once 28 | export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void) { 29 | const screenRef = ref(sizeEnum.XL); 30 | const realWidthRef = ref(window.innerWidth); 31 | 32 | function getWindowWidth() { 33 | const width = document.body.clientWidth; 34 | const xs = screenMap.get(sizeEnum.XS)!; 35 | const sm = screenMap.get(sizeEnum.SM)!; 36 | const md = screenMap.get(sizeEnum.MD)!; 37 | const lg = screenMap.get(sizeEnum.LG)!; 38 | const xl = screenMap.get(sizeEnum.XL)!; 39 | if (width < xs) { 40 | screenRef.value = sizeEnum.XS; 41 | } else if (width < sm) { 42 | screenRef.value = sizeEnum.SM; 43 | } else if (width < md) { 44 | screenRef.value = sizeEnum.MD; 45 | } else if (width < lg) { 46 | screenRef.value = sizeEnum.LG; 47 | } else if (width < xl) { 48 | screenRef.value = sizeEnum.XL; 49 | } else { 50 | screenRef.value = sizeEnum.XXL; 51 | } 52 | realWidthRef.value = width; 53 | } 54 | 55 | useEventListener({ 56 | el: window, 57 | name: 'resize', 58 | 59 | listener: () => { 60 | getWindowWidth(); 61 | resizeFn(); 62 | }, 63 | // wait: 100, 64 | }); 65 | 66 | getWindowWidth(); 67 | globalScreenRef = computed(() => unref(screenRef)); 68 | globalWidthRef = computed((): number => screenMap.get(unref(screenRef)!)!); 69 | globalRealWidthRef = computed((): number => unref(realWidthRef)); 70 | 71 | function resizeFn() { 72 | fn?.({ 73 | screen: globalScreenRef, 74 | width: globalWidthRef, 75 | realWidth: globalRealWidthRef, 76 | screenEnum, 77 | screenMap, 78 | sizeEnum, 79 | }); 80 | } 81 | 82 | resizeFn(); 83 | return { 84 | screenRef: globalScreenRef, 85 | screenEnum, 86 | widthRef: globalWidthRef, 87 | realWidthRef: globalRealWidthRef, 88 | }; 89 | } 90 | --------------------------------------------------------------------------------